Skip to content

build(deps): Bump scikit-learn from 1.3.2 to 1.5.0 in /packages/agent-os/modules/caas#4

Closed
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/pip/packages/agent-os/modules/caas/scikit-learn-1.5.0
Closed

build(deps): Bump scikit-learn from 1.3.2 to 1.5.0 in /packages/agent-os/modules/caas#4
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/pip/packages/agent-os/modules/caas/scikit-learn-1.5.0

Conversation

@dependabot
Copy link
Copy Markdown
Contributor

@dependabot dependabot Bot commented on behalf of github Mar 3, 2026

Bumps scikit-learn from 1.3.2 to 1.5.0.

Release notes

Sourced from scikit-learn's releases.

Scikit-learn 1.5.0

We're happy to announce the 1.5.0 release.

You can read the release highlights under https://scikit-learn.org/stable/auto_examples/release_highlights/plot_release_highlights_1_5_0.html and the long version of the change log under https://scikit-learn.org/stable/whats_new/v1.5.html

This version supports Python versions 3.9 to 3.12.

You can upgrade with pip as usual:

pip install -U scikit-learn

The conda-forge builds can be installed using:

conda install -c conda-forge scikit-learn

Scikit-learn 1.4.2

We're happy to announce the 1.4.2 release.

This release only includes support for numpy 2.

This version supports Python versions 3.9 to 3.12.

You can upgrade with pip as usual:

pip install -U scikit-learn

Scikit-learn 1.4.1.post1

We're happy to announce the 1.4.1.post1 release.

You can see the changelog here: https://scikit-learn.org/stable/whats_new/v1.4.html#version-1-4-1-post1

This version supports Python versions 3.9 to 3.12.

You can upgrade with pip as usual:

pip install -U scikit-learn

The conda-forge builds can be installed using:

conda install -c conda-forge scikit-learn

... (truncated)

Commits

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    You can disable automated security fix PRs for this repo from the Security Alerts page.

Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 1.3.2 to 1.5.0.
- [Release notes](https://github.com/scikit-learn/scikit-learn/releases)
- [Commits](scikit-learn/scikit-learn@1.3.2...1.5.0)

---
updated-dependencies:
- dependency-name: scikit-learn
  dependency-version: 1.5.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file python Pull requests that update python code labels Mar 3, 2026
@imran-siddique
Copy link
Copy Markdown
Contributor

Closing — will address dependency updates in bulk during pre-release cleanup.

@dependabot @github
Copy link
Copy Markdown
Contributor Author

dependabot Bot commented on behalf of github Mar 4, 2026

OK, I won't notify you again about this release, but will get in touch when a new version is available. If you'd rather skip all updates until the next major or minor version, let me know by commenting @dependabot ignore this major version or @dependabot ignore this minor version.

If you change your mind, just re-open this PR and I'll resolve any conflicts on it.

@dependabot dependabot Bot deleted the dependabot/pip/packages/agent-os/modules/caas/scikit-learn-1.5.0 branch March 4, 2026 16:31
imran-siddique added a commit that referenced this pull request Apr 1, 2026
… 37 files) (#684)

* fix(security): eliminate CI injection vectors and pin actions (#1)

- Move all github.event.* expressions from run: to env: blocks (CWE-94)
  - spell-check.yml: changed_files via env var
  - markdown-link-check.yml: changed_files via temp file input
  - ai-spec-drafter.yml: issue.number via env var
  - ai-test-generator.yml: pull_request.number via env var
  - ai-release-notes.yml: release.tag_name via env var
  - sbom.yml: release.tag_name via env var
- Redact secret scanner output to prevent secret leaks to CI logs (CWE-200)
- SHA-pin dtolnay/rust-toolchain (the only unpinned action) (CWE-829)
- Add missing permissions: block to markdown-link-check.yml (CWE-250)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(security): supply chain hardening — dep confusion, lockfiles, Dockerfile digest (#2)

- Fix dependency confusion: replace agent-primitives==0.1.0 with local
  file references in scak and iatp requirements.txt (CWE-427)
- Pin root Dockerfile base image to SHA digest (CWE-829)
- Generate missing package-lock.json for 4 npm packages (CWE-829):
  mcp-proxy, api, chrome extension, mastra-agentmesh
- Remove unsafe npm ci || npm install fallback in ESRP pipeline (CWE-829)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(security): Docker/infra hardening — CORS, Grafana, .dockerignore, CODEOWNERS (#3)

- Replace hardcoded Grafana admin passwords with env var refs in 7
  docker-compose files (CWE-798)
- Replace wildcard CORS allow_origins=[*] with env-driven origins
  in 6 production services (CWE-942)
- Add secret exclusion patterns (.env, *.key, *.pem, *.p12) to root
  and caas .dockerignore files (CWE-532)
- Add security contact, supported versions, and 90-day disclosure
  policy to SECURITY.md (CWE-693)
- Add CODEOWNERS rules for scripts/, Dockerfile, docker-compose*,
  .dockerignore, .clusterfuzzlite/ (CWE-862)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(security): code quality — XSS, Rust panics, example warnings (#4)

- Replace innerHTML with safe DOM APIs (textContent, createElement)
  in PolicyEditorPanel.ts and MetricsDashboardPanel.ts (CWE-79)
- Add HTML entity escaping for violation names in metrics dashboard
- Replace .unwrap() with .expect() on production RwLock/Mutex calls
  in policy.rs for clearer panic messages (CWE-252)
- Add INTENTIONALLY INSECURE warnings to test fixture code in
  github-reviewer example to prevent copy-paste propagation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
prashansapkota added a commit to prashansapkota/agent-governance-toolkit that referenced this pull request Apr 27, 2026
- Add session_id to GovernanceReceipt to prevent replay attacks by
  binding receipts to a specific execution context (Critical #1)
- Add trusted_keys parameter to verify_receipt_chain for signer
  public key validation against a trusted set (Critical microsoft#3)
- Add Unicode edge case tests: emoji, CJK, empty strings (Critical microsoft#4)
- Add --json output flag to verify_receipts.py for CI/CD integration
- 74 tests passing (9 new tests added)
imran-siddique pushed a commit that referenced this pull request Apr 27, 2026
* feat: offline-verifiable decision receipts (Ed25519 + JCS)

- Add parent_receipt_hash for per-tool-call hash chaining
- Enforce RFC 8785 JCS canonical JSON (ensure_ascii=False)
- Add verify_receipt_chain() for offline chain verification
- Add to_slsa_provenance() for SLSA v1.0 predicate emission
- Add CLI verifier (scripts/verify_receipts.py)
- Add tutorial (docs/tutorials/33-offline-verifiable-receipts.md)
- 65 tests passing

Closes #1499

* fix: address CodeQL and reviewer critical findings

- Fix CodeQL high: use urlparse hostname check instead of substring
  match for builder URL validation (Incomplete URL substring sanitization)
- Fix critical: verify_receipt_chain now flags unsigned receipts instead
  of silently skipping them, preventing unsigned receipt injection
- Update tests to verify the unsigned receipt detection behavior

* fix: address code-reviewer critical findings

- Add session_id to GovernanceReceipt to prevent replay attacks by
  binding receipts to a specific execution context (Critical #1)
- Add trusted_keys parameter to verify_receipt_chain for signer
  public key validation against a trusted set (Critical #3)
- Add Unicode edge case tests: emoji, CJK, empty strings (Critical #4)
- Add --json output flag to verify_receipts.py for CI/CD integration
- 74 tests passing (9 new tests added)

* fix: address second-round reviewer findings

- CLI verify_receipts.py: structured per-receipt JSON output with exit
  codes (0=ok, 1=chain error, 2=load error) and --json flag detail
- Tests: add Unicode edge cases (replacement char U+FFFD, Arabic RTL),
  SLSA schema field validation, inserted-receipt detection, and
  all-defaults unsigned receipt coverage (83 tests total)

* refactor: simplify and clean up receipt, adapter, tests, and CLI

- receipt.py: remove verbose docstrings; flatten to_slsa_provenance dict;
  tighten sign_receipt, verify_receipt, and verify_receipt_chain
- adapter.py: collapse CedarPolicyEvaluator init; remove redundant comments;
  shorten govern_tool_call and govern_and_execute
- verify_receipts.py: collapse _reconstruct and verify_chain; tighten main()
- test_receipt.py: shared _make_chain helper; collapse unicode cases into one
  parametrized test; merge duplicate fixtures; 583 → 280 lines, same coverage

* fix: address latest reviewer critical findings

- verify_receipt: raise ImportError instead of silently returning False
  when cryptography library is missing
- ReceiptSigningError: custom exception replaces generic RuntimeError in
  govern_tool_call for clearer failure context
- ReceiptStore.add: enforce receipt_id uniqueness to prevent replay injection
- verify_receipt_chain: validate signer_public_key is 64-char hex before
  trusted-key comparison to block malformed key bypass

---------

Co-authored-by: Prashan Sapkota <prashansapkota@users.noreply.github.com>
imran-siddique added a commit that referenced this pull request May 4, 2026
…1709)

Packages existing chaos engineering (adversarial playbooks) and
PromptDefenseEvaluator into a unified CLI surface:

  agt red-team scan <path>        - Scan prompts for defense gaps
  agt red-team attack             - Run adversarial playbooks
  agt red-team list-playbooks     - List available attack playbooks
  agt red-team report             - Full red-team assessment

Addresses Gartner gap #4 (agent security testing/red teaming) by
making AGT's existing capabilities discoverable via a single command.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
imran-siddique pushed a commit that referenced this pull request May 12, 2026
…ner execs (#1954)

The timeout watchdog inside ``run`` called ``container.kill()`` to
abort an over-budget exec. That kills the entire container,
destroying every guest-state artefact prior ``execute_code`` calls
in the same session built up — installed packages, /tmp files,
running daemons, mounted scratch space, all of it. A single timeout
on exec #5 effectively wiped exec #1-#4's accumulated state.

Two structural changes, both load-bearing:

1. Scope the timeout to the specific exec, not the whole container.
   The new ``_run_with_exec_timeout`` drives ``exec_create`` /
   ``exec_start`` through the low-level Docker API so we hold the
   exec_id. On timeout, ``exec_inspect`` gives us the PID and we
   send SIGKILL to that process via
   ``container.exec_run(['kill', '-9', pid])`` from inside the
   container. ``container.kill()`` is now a fallback that fires
   only when the PID is unavailable or the kill itself fails.

2. Serialise concurrent execs per container with a per-(agent,
   session) ``threading.Lock`` in ``self._exec_locks``. Without
   this, a timeout on exec A could disrupt an unrelated exec B
   running in parallel inside the same container. The lock entry
   is cleaned up alongside the container in ``destroy_session``.

For the test path: when only the high-level
``container.exec_run`` is mocked (the existing fixture's pattern),
the low-level API returns MagicMocks that aren't usable. The new
``_LowLevelExecUnavailable`` sentinel detects that case and falls
back to ``_run_with_legacy_timeout`` — which mirrors the prior
behaviour (``container.exec_run`` in a thread,
``container.kill()`` on timeout). Real Docker daemons always
return tuple output and never trip the fallback.

Adds two regression tests:
  - ``test_timeout_kills_exec_process_not_container`` — timeout
    fires; asserts ``container.kill`` was NOT called and the
    PID-targeted ``container.exec_run(['kill', '-9', '4242'])``
    WAS called
  - ``test_concurrent_runs_serialise_per_container`` — 4 threads
    concurrently call ``run`` against the same session; asserts
    max-in-flight is 1 (serialised by the per-container lock)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jackbatzner added a commit to jackbatzner/agent-governance-toolkit that referenced this pull request May 29, 2026
…se auth

Addresses Opus PR microsoft#2645 re-review finding microsoft#4 ("resolve_dispute is security
theater") and a tangential sweep finding (submit_dispute did not lock the
escrow against further releases).

Changes:

- release_escrow: outcome="failure" now requires the provider's token
  (or admin), not the requester's. A requester cannot unilaterally
  refund themselves by claiming failure; the dispute flow is the only
  way to contest a delivery. outcome="success" still requires the
  requester (acknowledging delivery) and outcome="dispute" requires
  either participant.
- submit_dispute (arbiter): now atomically marks the escrow as
  "disputed" via a new escrow.mark_escrow_disputed helper. Once a
  dispute is open, neither party can /release the escrow until the
  arbiter rules. Idempotent for already-disputed escrows; rejects
  terminal-state escrows with 400 ESCROW_ALREADY_RESOLVED.
- resolve_dispute (arbiter): no longer returns a fabricated 100-credit
  payout that never moves state. It now (a) looks up the escrow's
  actual locked credit total via escrow.get_escrow_credits, (b)
  computes the split, (c) calls escrow.disburse_disputed_escrow to
  actually move the credits and transition the escrow out of
  "disputed", and (d) emits a "dispute_resolved" compliance event.
  Reputation deltas remain advisory (documented in README) since real
  reputation wiring is out of scope.
- escrow: new helpers get_escrow_credits, mark_escrow_disputed,
  disburse_disputed_escrow. The disburse helper rejects splits that do
  not sum to the locked credit total (400 DISBURSEMENT_MISMATCH) so
  arbiter math errors fail loudly.

README: documents the per-outcome release auth model, the dispute
locking guarantee, and the reputation-still-advisory boundary.

Tests: 20/20 passing (3 new):
- test_release_outcome_failure_requires_provider_or_admin
- test_submit_dispute_locks_escrow_against_subsequent_release
- test_resolve_dispute_disburses_locked_credits_and_unlocks_escrow

GPT-5.5 re-review was clean (no blockers/warnings).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
jackbatzner pushed a commit to jackbatzner/agent-governance-toolkit that referenced this pull request May 29, 2026
…ILING

Red-team finding microsoft#4: IntentManager.check_action does not verify that the caller's agent_id matches the intent's agent_id, so agent B can reuse agent A's stored intent record to perform privileged actions under A's policy context.

Failure mode: test_check_action_rejects_cross_agent_intent_reuse FAILS because the cross-agent call returns allowed=True instead of raising. Fix in next commit.
jackbatzner pushed a commit to jackbatzner/agent-governance-toolkit that referenced this pull request May 29, 2026
Closes microsoft#4. Asserts intent.agent_id == caller agent_id in check_action. Red->Green: 1 failed -> 41 passed.
imran-siddique pushed a commit that referenced this pull request May 29, 2026
…outes (#2645)

* fix(cloud-board): add bearer auth, close credit-minting gap, harden routes

Adds a fail-closed bearer-token auth layer to the Nexus Cloud Board API
and resolves issues surfaced in the recent security review:

- New api/auth.py with admin and agent-scoped principals, SHA-256 +
  hmac.compare_digest token comparison, '<did>=<token>' agent token
  entries, 401 with WWW-Authenticate, and 503 when tokens are not
  configured.
- Registry: registration binds the request DID to the verification key,
  PUT enforces auth + proof-of-possession + DID match, DELETE requires
  scoped auth, GET/discover redact owner_id and contact for anonymous
  callers.
- Reputation: report and slash are admin-only; slash history is
  admin-only because it exposes evidence and trace_ids.
- Escrow: all mutating endpoints require auth, credits start at 0 (no
  self-minting), add_credits is admin-only and rejects non-positive
  amounts, raise_dispute now uses a JSON body.
- Arbiter: disputes require an existing escrow, bind the disputing party
  to the authenticated principal, store participant DIDs, restrict
  resolution to admins, and scope reads to participants.
- Compliance: events/stats/export/download/data-handling are admin-only.
- Route ordering fix: /discover, /sync, /leaderboard, /slashes were
  shadowed by /{agent_did} path-param routes.
- README documents env vars, deliberately public reads, and the
  demo-only security boundary.
- 14 pytest cases under tests/cloud_board/test_api_auth.py.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(cloud-board): close SCAK fail-open, require admin outcome on resolve

Addresses Opus review findings on PR #2645:

- Escrow release with require_scak=true no longer succeeds when
  scak_drift_score is omitted. Missing drift score now returns 400
  SCAK_DRIFT_SCORE_REQUIRED instead of falling through to the success
  path. Drift above the threshold still resolves as failure.
- Arbiter resolve_dispute now requires an admin-supplied outcome
  (requester_wins | provider_wins | split) plus optional explanation.
  The arbiter no longer derives the winner from claimed_outcome (which
  is supplied by the disputing party at submit time and is therefore
  attacker-influenced).
- Arbiter get_resolution now returns the resolution record actually
  stored by resolve_dispute. It 404s with RESOLUTION_NOT_FOUND before
  the dispute is resolved, instead of returning a hardcoded 50/50
  split with a fabricated explanation.
- Three regression tests added (now 17 total): SCAK release without
  drift score is rejected; resolve_dispute without/with bad outcome is
  rejected and admin outcome is recorded; get_resolution 404s before
  resolve and returns the stored outcome after.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(cloud-board): wire arbiter to escrow state machine, tighten release auth

Addresses Opus PR #2645 re-review finding #4 ("resolve_dispute is security
theater") and a tangential sweep finding (submit_dispute did not lock the
escrow against further releases).

Changes:

- release_escrow: outcome="failure" now requires the provider's token
  (or admin), not the requester's. A requester cannot unilaterally
  refund themselves by claiming failure; the dispute flow is the only
  way to contest a delivery. outcome="success" still requires the
  requester (acknowledging delivery) and outcome="dispute" requires
  either participant.
- submit_dispute (arbiter): now atomically marks the escrow as
  "disputed" via a new escrow.mark_escrow_disputed helper. Once a
  dispute is open, neither party can /release the escrow until the
  arbiter rules. Idempotent for already-disputed escrows; rejects
  terminal-state escrows with 400 ESCROW_ALREADY_RESOLVED.
- resolve_dispute (arbiter): no longer returns a fabricated 100-credit
  payout that never moves state. It now (a) looks up the escrow's
  actual locked credit total via escrow.get_escrow_credits, (b)
  computes the split, (c) calls escrow.disburse_disputed_escrow to
  actually move the credits and transition the escrow out of
  "disputed", and (d) emits a "dispute_resolved" compliance event.
  Reputation deltas remain advisory (documented in README) since real
  reputation wiring is out of scope.
- escrow: new helpers get_escrow_credits, mark_escrow_disputed,
  disburse_disputed_escrow. The disburse helper rejects splits that do
  not sum to the locked credit total (400 DISBURSEMENT_MISMATCH) so
  arbiter math errors fail loudly.

README: documents the per-outcome release auth model, the dispute
locking guarantee, and the reputation-still-advisory boundary.

Tests: 20/20 passing (3 new):
- test_release_outcome_failure_requires_provider_or_admin
- test_submit_dispute_locks_escrow_against_subsequent_release
- test_resolve_dispute_disburses_locked_credits_and_unlocks_escrow

GPT-5.5 re-review was clean (no blockers/warnings).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(cloud-board): RED — bearer-auth oracle + env-cache regressions (F#3,4,10,15)

Pre-fix failure modes: 5 RED (403 vs 401 oracle on require_admin x4 endpoints; 503 vs 200 on admin plane when one env entry is malformed); 1 invariant-pin (bearer-cap behavior is response-code identical pre/post since both reject, but the test pins the cap regression-side).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(cloud-board): harden bearer auth (F#3 oracle, F#4 cache, F#10 doc, F#15 length cap)

GREEN: 6/6 group-1 regression tests now pass.

- F#3: require_admin returns uniform 401 (drops 403-on-valid-agent-token oracle)

- F#4: cache parsed agent-token env entries; malformed entries log+continue instead of 503ing every request

- F#10: document comma-in-token limitation

- F#15: refuse bearer tokens > 256 bytes before SHA-256 (DoS hardening)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test(cloud-board): RED — escrow double-pay + fail-closed regressions (F#1,2,5,7,8,9,12)

Pre-fix failure modes: 9 RED

- test_raise_dispute_rejects_terminal_escrow_no_double_payout: 200 != 400 (terminal escrow re-disputable, full create->release->dispute->resolve chain inflates total credits)

- test_disburse_disputed_escrow_refuses_second_payout: DID NOT RAISE (second disburse succeeds, doubling provider credits)

- test_scak_drift_score_rejects_non_finite_values[nan/inf/-inf]: DID NOT RAISE (validator absent on baseline)

- test_create_escrow_rejects_self_escrow: 200 != 400 (self-escrow accepted)

- test_create_escrow_rejects_unregistered_provider: 200 != 400 (no registration check)

- test_unauthorized_escrow_access_returns_404_not_403: 403 != 404 (oracle distinguishes participant vs non-participant)

- test_dispute_reason_capped_on_release_dispute: 200 != 422 (no length cap)

- 1 invariant-pin (release_dispute_branch_preserves_audit_reason) for F#7 defense-in-depth

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(cloud-board): close escrow double-pay + fail-closed validators (F#1,2,5,7,8,9,12)

GREEN: 10/10 group-2 regression tests now pass; full suite 32/32.

- F#1: raise_dispute refuses terminal states; idempotent already-disputed preserves reason; disburse_disputed_escrow rejects if resolved_at set (3 layered defenses)

- F#2: ReleaseEscrowRequest rejects NaN/+Inf/-Inf scak_drift_score via field_validator

- F#5: _authorize_escrow_participant returns 404 (not 403)

- F#7: release(outcome=dispute) preserves prior dispute_reason instead of clobbering with None

- F#8: ReleaseEscrowRequest.dispute_reason capped at 1000 chars

- F#9: create_escrow rejects requester_did == provider_did (SELF_ESCROW_FORBIDDEN)

- F#12: create_escrow rejects unregistered provider (PROVIDER_NOT_REGISTERED)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test(cloud-board): RED — arbiter dispute lifecycle regressions (F#5,6,8,14,17)

Pre-fix failure modes: 6 RED — 403!=404 oracle on dispute GET, 200!=409 on duplicate submit, KeyError submitted_by, 403!=404 oracle on submit, 200!=422 reason cap, orphan dispute not marked terminal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(cloud-board): tighten arbiter dispute lifecycle (F#5,6,8,14,17)

GREEN: 6/6 group-3 regression tests now pass.

- F#5: dispute participant checks return 404 (not 403)

- F#6: reject duplicate open disputes for same escrow (409 DISPUTE_ALREADY_OPEN)

- F#8: SubmitDisputeRequest.dispute_reason length-capped at 1000

- F#14: submit_dispute records submitted_by (agent DID or 'admin')

- F#17: resolve_dispute on missing escrow marks dispute terminal before 409

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test(cloud-board): RED — registry hardening regressions (F#3,11,16)

Pre-fix failure modes: 3 RED

- test_get_agent_redacts_pii_for_other_authenticated_callers: owner_id leaks to PROVIDER (non-owner authenticated caller) due to denylist redaction

- test_registration_rejects_naive_proof_timestamp: 500 TypeError 'can't subtract offset-naive and offset-aware datetimes' instead of 400

- test_did_now_uses_full_256_bit_sha256: full 64-char DID rejected as DID_MISMATCH because baseline truncates to 32 chars

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(cloud-board): registry hardening (F#3 PII allowlist, F#11 tz, F#16 256-bit DID)

GREEN: 3/3 group-4 regression tests now pass; full suite 39/39.

- F#3: _view_manifest uses an allowlist (did, verification_key, display_name); full identity only for owner or admin

- F#11: register/update_agent reject naive timestamps with 400 INVALID_TIMESTAMP

- F#16: derived DID uses full 64-hex-char SHA-256 (256-bit) instead of 128-bit truncation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(cloud-board): document reputation read asymmetry + PII redaction model (F#13)

Also fixes ruff W292 missing trailing newline in test_api_auth.py.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jackbatzner pushed a commit to jackbatzner/agent-governance-toolkit that referenced this pull request May 29, 2026
…ILING

Red-team finding microsoft#4: IntentManager.check_action does not verify that the caller's agent_id matches the intent's agent_id, so agent B can reuse agent A's stored intent record to perform privileged actions under A's policy context.

Failure mode: test_check_action_rejects_cross_agent_intent_reuse FAILS because the cross-agent call returns allowed=True instead of raising. Fix in next commit.
jackbatzner pushed a commit to jackbatzner/agent-governance-toolkit that referenced this pull request May 29, 2026
Closes microsoft#4. Asserts intent.agent_id == caller agent_id in check_action. Red->Green: 1 failed -> 41 passed.
jackbatzner pushed a commit to jackbatzner/agent-governance-toolkit that referenced this pull request May 30, 2026
…ILING

Red-team finding microsoft#4: IntentManager.check_action does not verify that the caller's agent_id matches the intent's agent_id, so agent B can reuse agent A's stored intent record to perform privileged actions under A's policy context.

Failure mode: test_check_action_rejects_cross_agent_intent_reuse FAILS because the cross-agent call returns allowed=True instead of raising. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
jackbatzner pushed a commit to jackbatzner/agent-governance-toolkit that referenced this pull request May 30, 2026
Closes microsoft#4. Asserts intent.agent_id == caller agent_id in check_action. Red->Green: 1 failed -> 41 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
jackbatzner pushed a commit to jackbatzner/agent-governance-toolkit that referenced this pull request May 30, 2026
…ILING

Red-team finding microsoft#4: IntentManager.check_action does not verify that the caller's agent_id matches the intent's agent_id, so agent B can reuse agent A's stored intent record to perform privileged actions under A's policy context.

Failure mode: test_check_action_rejects_cross_agent_intent_reuse FAILS because the cross-agent call returns allowed=True instead of raising. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
jackbatzner pushed a commit to jackbatzner/agent-governance-toolkit that referenced this pull request May 30, 2026
Closes microsoft#4. Asserts intent.agent_id == caller agent_id in check_action. Red->Green: 1 failed -> 41 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
imran-siddique pushed a commit that referenced this pull request May 30, 2026
…xecute API (#2644)

* fix(agent-os): close authorization bypasses in stateless kernel and execute API

Three same-class authorization fixes identified in security review:

1. stateless._check_policies: caller-supplied params['approved']=True no longer satisfies requires_approval gates. Approval must flow through the trusted IntentManager path; unplanned drift on restricted actions is now denied. The legacy flag is stripped from params before action execution.

2. server/app.py /api/v1/execute: caller-supplied agent_id is no longer trusted when authentication is bypassed. The legacy AGENT_OS_ALLOW_UNAUTHENTICATED_EXECUTE env var now raises ValueError at construction time. The replacement AGENT_OS_UNSAFE_ALLOW_UNAUTHENTICATED_EXECUTE is gated on AGENT_OS_ENV in {dev,development,local}; the server-side identity is fixed by AGENT_OS_UNSAFE_LOCAL_EXECUTE_AGENT_ID (default local-dev-agent); mismatched caller agent_id is rejected with 422 (unsafe) or 403 (authenticated).

3. mcp-kernel-server KernelExecuteTool._check_policies: same params.get('approved') bypass pattern as (1); now ignored with a warning log and the action is denied with guidance pointing to a trusted host approval workflow.

Tests added/updated for all three paths. Tangential sweep covered other auth surfaces (mcp_gateway approval callback, AGENT_OS_* env vars, REST endpoints) and found no further in-class bugs in agent-os core; module-level FastAPI surfaces in caas/iatp/observability are out of scope for this PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(mcp-scan): regression for env-poisoning RCE + cwd hijack -- currently FAILING

Red-team findings #1 + #2: mcp-scan CLI accepts arbitrary environment keys (LD_PRELOAD, PYTHONPATH, NODE_OPTIONS, ...) and untrusted cwd paths when launching subprocesses, enabling pre-exec code injection.

These regression tests assert the SECURE behavior (refusal). They FAIL on this commit because the helpers _blocked_command_env_keys and _validate_launch_cwd do not exist, proving the vuln surface is present.

Failure mode: 28 errors in TestLaunchEnvAndCwdGuards (AttributeError on missing helpers). Fix applied in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(mcp-scan): restore env-key blocklist and untrusted-cwd guard

Closes red-team findings #1 + #2. Restores _blocked_command_env_keys and _validate_launch_cwd helpers. Red->Green: 28 errors -> 129 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(authz): regression for approval-key bypasses + provider edge cases -- currently FAILING

Red-team findings #8 (confusable/nested approved keys bypass strip), #10 (non-strict-True provider return treated as allow), #11 (log injection via CR/LF in caller fields), #12 (provider BaseException leaks past approval check).

Failure mode: 15 failures across stateless + mcp_kernel_server.tools. Cyrillic 'approvеd', uppercased 'Approved', nested dict values, truthy-non-bool returns ('yes', 1, object), and SystemExit/KeyboardInterrupt all currently bypass the gate. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(authz): harden approval-key strip, strict-bool, BaseException, log sanitization

Closes red-team #8, #10, #11, #12. NFKC + casefold approved-key match, recursive strip into nested dicts/lists, strict 'is True', except BaseException, _sanitize_log_field. Red->Green: 15 failed -> 141 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(authz): regression for empty-policies bypass + non-loopback execute -- currently FAILING

Red-team findings #3 (no policy match -> action allowed even when requires_approval declared elsewhere) and #5 (unsafe execute mode trusted from arbitrary remote peers).

Failure mode: test_execute_global_approval_blocks_empty_policy_list FAILS because StatelessKernel falls through to allow when no policy entry matches. test_execute_unsafe_escape_hatch_rejects_non_loopback_peer FAILS because _authenticate_execute_request does not inspect request.client. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(authz): close empty-policies bypass and enforce loopback for unsafe execute

Closes #3 + #5. _globally_protected_actions enforced after per-policy loop; _is_loopback_client rejects non-127.x/::1 peers with 403. Red->Green: 2 failed -> 94 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(intent): regression for cross-agent intent reuse -- currently FAILING

Red-team finding #4: IntentManager.check_action does not verify that the caller's agent_id matches the intent's agent_id, so agent B can reuse agent A's stored intent record to perform privileged actions under A's policy context.

Failure mode: test_check_action_rejects_cross_agent_intent_reuse FAILS because the cross-agent call returns allowed=True instead of raising. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(intent): bind intent to declaring agent_id

Closes #4. Asserts intent.agent_id == caller agent_id in check_action. Red->Green: 1 failed -> 41 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(iatp): regression for weak/short trusted-override tokens -- currently FAILING

Red-team finding #9: AGENT_OS_IATP_TRUSTED_OVERRIDE_TOKEN accepts any non-empty string -- 'true', 'admin', 'password', 'x' -- so a misconfigured operator (or attacker who can set one env var) trivially enables the X-User-Override path.

Failure mode: 18 failures in test_blacklisted_weak_token_disables_gate (main+sidecar paths) and test_short_token_disables_gate. Each demonstrates a weak/short token still bypassing the override check. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(iatp): reject weak/short trusted-override tokens

Closes #9. _load_trusted_override_token enforces 16-char minimum and blacklists {true,yes,admin,password,...}. Sidecar delegates to iatp.main to prevent drift. Red->Green: 18 failed -> 30 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(policies): regression for plaintext OPA over network -- currently FAILING

Red-team finding #7: OPABackend remote mode follows http:// URLs to non-loopback hosts without warning. An on-path attacker on the OPA route flips allow=true and the kernel approves any action.

Failure mode: test_plaintext_remote_non_loopback_denied and test_plaintext_opt_in_without_local_env_denied FAIL because _evaluate_remote performs the HTTP call without protocol gating. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(policies): require HTTPS for remote OPA unless explicitly opted in

Closes #7. _evaluate_remote rejects non-HTTPS unless loopback host OR (AGENT_OS_OPA_ALLOW_PLAINTEXT=1 + AGENT_OS_ENV in {local,dev,development}). Plaintext non-loopback returns error='plaintext_opa_blocked'. Red->Green: 2 failed -> 77 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(caas): regression for unauthenticated FastAPI surface gate -- currently FAILING

Red-team finding #6: caas.api.server only LOGS a warning when started outside local env; misconfigured deployment exposes every CaaS route silently.

Failure mode: 13 failures because _caas_unauth_gate_satisfied does not exist and startup hook does not raise. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(caas): require explicit env gate to start unauthenticated CaaS surface

Closes #6. Startup hook raises RuntimeError unless AGENT_OS_ENV in {local,dev,development} OR CAAS_UNSAFE_ALLOW_UNAUTH=1. Red->Green: 13 failed -> 13 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* ci(agent-os): clear no-stubs/no-crypto/spell-check/safety-critical CI gates

- Reword TODO(security) doc comments to 'Future hardening (security)' in caas/api/server.py, iatp/main.py (x2 including proxy_task cross-ref), iatp/sidecar/__init__.py so the no-stubs CI gate accepts the docs without losing the design-followup intent.

- Replace inline 'import hmac; hmac.compare_digest' with 'import secrets; secrets.compare_digest' in iatp/main.py so the no-custom-crypto CI gate is happy (secrets.compare_digest is the stdlib re-export of hmac.compare_digest, same constant-time guarantee).

- Add 19 project-specific terms to .cspell-repo-terms.txt (ASGI, NFKC, casefold, confusables, multitenant, normalisation, sanitised, unicodedata, testclient, monkeypatched, baseexception, rsplit, hdrs, oncall, madmin, backendunavailable, changeme, shortone, approv) for the spell-check-changed-files job.

- Update tests/test_safety_critical.py::TestPolicyEdgeCases::test_empty_policies_list_allows to reflect the new fail-closed behavior from fix #3: an empty policies list must DENY requires_approval actions (file_write). Renamed to test_empty_policies_list_denies_protected_actions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* ci(spell-check): allow cyrillic-e 'approv\u0435d' confusable used in unicode normalization tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

---------

Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
MohammadHaroonAbuomar pushed a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
… 37 files) (microsoft#684)

* fix(security): eliminate CI injection vectors and pin actions (microsoft#1)

- Move all github.event.* expressions from run: to env: blocks (CWE-94)
  - spell-check.yml: changed_files via env var
  - markdown-link-check.yml: changed_files via temp file input
  - ai-spec-drafter.yml: issue.number via env var
  - ai-test-generator.yml: pull_request.number via env var
  - ai-release-notes.yml: release.tag_name via env var
  - sbom.yml: release.tag_name via env var
- Redact secret scanner output to prevent secret leaks to CI logs (CWE-200)
- SHA-pin dtolnay/rust-toolchain (the only unpinned action) (CWE-829)
- Add missing permissions: block to markdown-link-check.yml (CWE-250)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(security): supply chain hardening — dep confusion, lockfiles, Dockerfile digest (microsoft#2)

- Fix dependency confusion: replace agent-primitives==0.1.0 with local
  file references in scak and iatp requirements.txt (CWE-427)
- Pin root Dockerfile base image to SHA digest (CWE-829)
- Generate missing package-lock.json for 4 npm packages (CWE-829):
  mcp-proxy, api, chrome extension, mastra-agentmesh
- Remove unsafe npm ci || npm install fallback in ESRP pipeline (CWE-829)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(security): Docker/infra hardening — CORS, Grafana, .dockerignore, CODEOWNERS (microsoft#3)

- Replace hardcoded Grafana admin passwords with env var refs in 7
  docker-compose files (CWE-798)
- Replace wildcard CORS allow_origins=[*] with env-driven origins
  in 6 production services (CWE-942)
- Add secret exclusion patterns (.env, *.key, *.pem, *.p12) to root
  and caas .dockerignore files (CWE-532)
- Add security contact, supported versions, and 90-day disclosure
  policy to SECURITY.md (CWE-693)
- Add CODEOWNERS rules for scripts/, Dockerfile, docker-compose*,
  .dockerignore, .clusterfuzzlite/ (CWE-862)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(security): code quality — XSS, Rust panics, example warnings (microsoft#4)

- Replace innerHTML with safe DOM APIs (textContent, createElement)
  in PolicyEditorPanel.ts and MetricsDashboardPanel.ts (CWE-79)
- Add HTML entity escaping for violation names in metrics dashboard
- Replace .unwrap() with .expect() on production RwLock/Mutex calls
  in policy.rs for clearer panic messages (CWE-252)
- Add INTENTIONALLY INSECURE warnings to test fixture code in
  github-reviewer example to prevent copy-paste propagation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MohammadHaroonAbuomar pushed a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
…#1519)

* feat: offline-verifiable decision receipts (Ed25519 + JCS)

- Add parent_receipt_hash for per-tool-call hash chaining
- Enforce RFC 8785 JCS canonical JSON (ensure_ascii=False)
- Add verify_receipt_chain() for offline chain verification
- Add to_slsa_provenance() for SLSA v1.0 predicate emission
- Add CLI verifier (scripts/verify_receipts.py)
- Add tutorial (docs/tutorials/33-offline-verifiable-receipts.md)
- 65 tests passing

Closes microsoft#1499

* fix: address CodeQL and reviewer critical findings

- Fix CodeQL high: use urlparse hostname check instead of substring
  match for builder URL validation (Incomplete URL substring sanitization)
- Fix critical: verify_receipt_chain now flags unsigned receipts instead
  of silently skipping them, preventing unsigned receipt injection
- Update tests to verify the unsigned receipt detection behavior

* fix: address code-reviewer critical findings

- Add session_id to GovernanceReceipt to prevent replay attacks by
  binding receipts to a specific execution context (Critical microsoft#1)
- Add trusted_keys parameter to verify_receipt_chain for signer
  public key validation against a trusted set (Critical microsoft#3)
- Add Unicode edge case tests: emoji, CJK, empty strings (Critical microsoft#4)
- Add --json output flag to verify_receipts.py for CI/CD integration
- 74 tests passing (9 new tests added)

* fix: address second-round reviewer findings

- CLI verify_receipts.py: structured per-receipt JSON output with exit
  codes (0=ok, 1=chain error, 2=load error) and --json flag detail
- Tests: add Unicode edge cases (replacement char U+FFFD, Arabic RTL),
  SLSA schema field validation, inserted-receipt detection, and
  all-defaults unsigned receipt coverage (83 tests total)

* refactor: simplify and clean up receipt, adapter, tests, and CLI

- receipt.py: remove verbose docstrings; flatten to_slsa_provenance dict;
  tighten sign_receipt, verify_receipt, and verify_receipt_chain
- adapter.py: collapse CedarPolicyEvaluator init; remove redundant comments;
  shorten govern_tool_call and govern_and_execute
- verify_receipts.py: collapse _reconstruct and verify_chain; tighten main()
- test_receipt.py: shared _make_chain helper; collapse unicode cases into one
  parametrized test; merge duplicate fixtures; 583 → 280 lines, same coverage

* fix: address latest reviewer critical findings

- verify_receipt: raise ImportError instead of silently returning False
  when cryptography library is missing
- ReceiptSigningError: custom exception replaces generic RuntimeError in
  govern_tool_call for clearer failure context
- ReceiptStore.add: enforce receipt_id uniqueness to prevent replay injection
- verify_receipt_chain: validate signer_public_key is 64-char hex before
  trusted-key comparison to block malformed key bypass

---------

Co-authored-by: Prashan Sapkota <prashansapkota@users.noreply.github.com>
MohammadHaroonAbuomar added a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
Both reviewers (claude-opus-4.7-1m-internal + gpt-5.5) found
overlapping concerns. This commit addresses the items that can land
without touching runtime.rs.

Blockers fixed:
  - manifest.schema.json missing cedar branch (GPT microsoft#3). Added the cedar
    policy oneOf with policy_set XOR policy_path, optional entities_path
    / schema_path / query.
  - Evidence over 4 KiB silently accepted (GPT microsoft#2 partial). Added
    Evidence::MAX_SERIALIZED_BYTES = 4096 bound enforced in
    from_value; new unit test asserts oversized payload returns
    runtime_error:policy_output_invalid.

Warnings fixed:
  - Rust RuntimeError lacked the four resolution_* variants Python
    already exposes (GPT microsoft#4 / D6 cross-language parity). Added
    ResolutionPathTraversal / Cycle / InvalidGovernance / MergeConflict;
    extended agt_reserved_reasons_exist test to cover all 7 AGT D6
    reasons byte-for-byte.
  - agt-policies build.py silently dropped rules with unsupported
    operators (Opus microsoft#6). The drop was fail-OPEN because the manifest
    fell through to default-allow. Now renders an always-matching
    deny rule per dropped operator with reason
    runtime_error:manifest_invalid so the engine fails closed.
  - Decision::applies_effects() included Escalate (Opus microsoft#7). Spec §13.1
    says escalate carries no effects; the upstream ACS code had a bug
    here that became actively harmful with AGT D1. Removed Escalate;
    explicit Transform also returns false (uses verdict.transform
    instead). Parity fixture + test updated to match.
  - DELTA / AGT-SNAPSHOT documented the IFC library replacement as
    'MUST replace' the upstream file (Opus microsoft#5). Reframed as 'AGT ships
    agt_ifc.rego alongside upstream ifc.rego'; AGT users MUST import
    data.agt.ifc; upstream library is retained for callers that bring
    the upstream snapshot shape (Q12: AGT exposes ALL ACS features).

Remaining round-1 blockers (deferred to a focused follow-up):
  - Transform verdict parsed at normalization but NOT applied to the
    policy target at the engine level (Opus/GPT microsoft#1). Adding the
    application path requires changes to runtime.rs::evaluate_intervention_point.
  - Effects[] still accepted/applied by the engine (Opus microsoft#2). D1 says
    MUST reject. Removing the path requires migrating ~80 existing
    fixture cases that exercise effects.
  - Evidence telemetry propagation (Opus microsoft#3 / GPT remaining): the
    runtime needs to attach evidence_artefact and
    evidence_verification_pointer_keys to decision events, and emit
    intervention_point.transformed instead of effect_applied.
  - Bisected action identity (Opus microsoft#4 warning): runtime needs to
    compute input_identity AND enforced_identity for transform verdicts.

These four cluster around the same Rust file (runtime.rs +
telemetry.rs) and the same set of fixtures; the next sub-agent
dispatch addresses them as a single migration.

Test totals after this commit: pytest 44, cargo 170, opa 98 = 312.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
MohammadHaroonAbuomar added a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
… _bridge

Round-4 Opus regression: the previous round-4 fix (2604ea0) incremented
``self._adapter_ctx.call_count`` AND called
``self._bridge.record_post_execute(tool_calls=1)``, but both mutations
ultimately advance the SnapshotBuilder's ``tool_call_count``:

- ``AdapterRuntimeBridge.builder_for(ctx)`` mirrors
  ``builder.tool_call_count = max(builder.tool_call_count, ctx.call_count)``
  on every call (`_v5_runtime_bridge.py:188`).
- ``record_post_execute(tool_calls=1)`` then adds another 1 to the same
  builder via ``record_tool_call``.

Result: after 3 sequential non-sensitive calls through the same
``_bridge``, the rego saw ``tool_call_count`` go 0 → 2 → 3 instead of
0 → 1 → 2. A ``GovernancePolicy(max_tool_calls=5)`` would deny on call
microsoft#4, not call microsoft#5 — a silent off-by-one against the documented
``max_tool_calls`` contract.

The smolagents adapter already warned about this anti-pattern
(`smolagents_adapter.py:734-738` / `:1032-1035` comments) and uses the
single-mutation pattern. The fix here adopts that pattern: drop both
``record_post_execute`` calls. The single ``ctx.call_count += 1``
propagates to both the default ``_bridge`` and the sibling
``_approval_bridge`` (when present) via the ``builder_for`` mirror,
which is what closed the original GPT round-3 budget-divergence
regression.

The new regression test
``test_repeated_non_sensitive_calls_do_not_double_count_budget``
asserts three sequential calls produce ``tool_call_count == [0, 1, 2]``
in the order the rego dispatcher observes. Test fails against pre-fix
code (observes ``[0, 2, 3]``), passes against post-fix.

Tested: 13/13 TestGoogleADKBridgeScenarios + 251 test_integrations.py
+ 10/10 demo. No regressions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Mohamed AbuOmar <mhabuomar@users.noreply.github.com>
MohammadHaroonAbuomar pushed a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
…icrosoft#1709)

Packages existing chaos engineering (adversarial playbooks) and
PromptDefenseEvaluator into a unified CLI surface:

  agt red-team scan <path>        - Scan prompts for defense gaps
  agt red-team attack             - Run adversarial playbooks
  agt red-team list-playbooks     - List available attack playbooks
  agt red-team report             - Full red-team assessment

Addresses Gartner gap microsoft#4 (agent security testing/red teaming) by
making AGT's existing capabilities discoverable via a single command.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MohammadHaroonAbuomar pushed a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
…ner execs (microsoft#1954)

The timeout watchdog inside ``run`` called ``container.kill()`` to
abort an over-budget exec. That kills the entire container,
destroying every guest-state artefact prior ``execute_code`` calls
in the same session built up — installed packages, /tmp files,
running daemons, mounted scratch space, all of it. A single timeout
on exec microsoft#5 effectively wiped exec microsoft#1-microsoft#4's accumulated state.

Two structural changes, both load-bearing:

1. Scope the timeout to the specific exec, not the whole container.
   The new ``_run_with_exec_timeout`` drives ``exec_create`` /
   ``exec_start`` through the low-level Docker API so we hold the
   exec_id. On timeout, ``exec_inspect`` gives us the PID and we
   send SIGKILL to that process via
   ``container.exec_run(['kill', '-9', pid])`` from inside the
   container. ``container.kill()`` is now a fallback that fires
   only when the PID is unavailable or the kill itself fails.

2. Serialise concurrent execs per container with a per-(agent,
   session) ``threading.Lock`` in ``self._exec_locks``. Without
   this, a timeout on exec A could disrupt an unrelated exec B
   running in parallel inside the same container. The lock entry
   is cleaned up alongside the container in ``destroy_session``.

For the test path: when only the high-level
``container.exec_run`` is mocked (the existing fixture's pattern),
the low-level API returns MagicMocks that aren't usable. The new
``_LowLevelExecUnavailable`` sentinel detects that case and falls
back to ``_run_with_legacy_timeout`` — which mirrors the prior
behaviour (``container.exec_run`` in a thread,
``container.kill()`` on timeout). Real Docker daemons always
return tuple output and never trip the fallback.

Adds two regression tests:
  - ``test_timeout_kills_exec_process_not_container`` — timeout
    fires; asserts ``container.kill`` was NOT called and the
    PID-targeted ``container.exec_run(['kill', '-9', '4242'])``
    WAS called
  - ``test_concurrent_runs_serialise_per_container`` — 4 threads
    concurrently call ``run`` against the same session; asserts
    max-in-flight is 1 (serialised by the per-container lock)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MohammadHaroonAbuomar pushed a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
…outes (microsoft#2645)

* fix(cloud-board): add bearer auth, close credit-minting gap, harden routes

Adds a fail-closed bearer-token auth layer to the Nexus Cloud Board API
and resolves issues surfaced in the recent security review:

- New api/auth.py with admin and agent-scoped principals, SHA-256 +
  hmac.compare_digest token comparison, '<did>=<token>' agent token
  entries, 401 with WWW-Authenticate, and 503 when tokens are not
  configured.
- Registry: registration binds the request DID to the verification key,
  PUT enforces auth + proof-of-possession + DID match, DELETE requires
  scoped auth, GET/discover redact owner_id and contact for anonymous
  callers.
- Reputation: report and slash are admin-only; slash history is
  admin-only because it exposes evidence and trace_ids.
- Escrow: all mutating endpoints require auth, credits start at 0 (no
  self-minting), add_credits is admin-only and rejects non-positive
  amounts, raise_dispute now uses a JSON body.
- Arbiter: disputes require an existing escrow, bind the disputing party
  to the authenticated principal, store participant DIDs, restrict
  resolution to admins, and scope reads to participants.
- Compliance: events/stats/export/download/data-handling are admin-only.
- Route ordering fix: /discover, /sync, /leaderboard, /slashes were
  shadowed by /{agent_did} path-param routes.
- README documents env vars, deliberately public reads, and the
  demo-only security boundary.
- 14 pytest cases under tests/cloud_board/test_api_auth.py.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(cloud-board): close SCAK fail-open, require admin outcome on resolve

Addresses Opus review findings on PR microsoft#2645:

- Escrow release with require_scak=true no longer succeeds when
  scak_drift_score is omitted. Missing drift score now returns 400
  SCAK_DRIFT_SCORE_REQUIRED instead of falling through to the success
  path. Drift above the threshold still resolves as failure.
- Arbiter resolve_dispute now requires an admin-supplied outcome
  (requester_wins | provider_wins | split) plus optional explanation.
  The arbiter no longer derives the winner from claimed_outcome (which
  is supplied by the disputing party at submit time and is therefore
  attacker-influenced).
- Arbiter get_resolution now returns the resolution record actually
  stored by resolve_dispute. It 404s with RESOLUTION_NOT_FOUND before
  the dispute is resolved, instead of returning a hardcoded 50/50
  split with a fabricated explanation.
- Three regression tests added (now 17 total): SCAK release without
  drift score is rejected; resolve_dispute without/with bad outcome is
  rejected and admin outcome is recorded; get_resolution 404s before
  resolve and returns the stored outcome after.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(cloud-board): wire arbiter to escrow state machine, tighten release auth

Addresses Opus PR microsoft#2645 re-review finding microsoft#4 ("resolve_dispute is security
theater") and a tangential sweep finding (submit_dispute did not lock the
escrow against further releases).

Changes:

- release_escrow: outcome="failure" now requires the provider's token
  (or admin), not the requester's. A requester cannot unilaterally
  refund themselves by claiming failure; the dispute flow is the only
  way to contest a delivery. outcome="success" still requires the
  requester (acknowledging delivery) and outcome="dispute" requires
  either participant.
- submit_dispute (arbiter): now atomically marks the escrow as
  "disputed" via a new escrow.mark_escrow_disputed helper. Once a
  dispute is open, neither party can /release the escrow until the
  arbiter rules. Idempotent for already-disputed escrows; rejects
  terminal-state escrows with 400 ESCROW_ALREADY_RESOLVED.
- resolve_dispute (arbiter): no longer returns a fabricated 100-credit
  payout that never moves state. It now (a) looks up the escrow's
  actual locked credit total via escrow.get_escrow_credits, (b)
  computes the split, (c) calls escrow.disburse_disputed_escrow to
  actually move the credits and transition the escrow out of
  "disputed", and (d) emits a "dispute_resolved" compliance event.
  Reputation deltas remain advisory (documented in README) since real
  reputation wiring is out of scope.
- escrow: new helpers get_escrow_credits, mark_escrow_disputed,
  disburse_disputed_escrow. The disburse helper rejects splits that do
  not sum to the locked credit total (400 DISBURSEMENT_MISMATCH) so
  arbiter math errors fail loudly.

README: documents the per-outcome release auth model, the dispute
locking guarantee, and the reputation-still-advisory boundary.

Tests: 20/20 passing (3 new):
- test_release_outcome_failure_requires_provider_or_admin
- test_submit_dispute_locks_escrow_against_subsequent_release
- test_resolve_dispute_disburses_locked_credits_and_unlocks_escrow

GPT-5.5 re-review was clean (no blockers/warnings).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(cloud-board): RED — bearer-auth oracle + env-cache regressions (F#3,4,10,15)

Pre-fix failure modes: 5 RED (403 vs 401 oracle on require_admin x4 endpoints; 503 vs 200 on admin plane when one env entry is malformed); 1 invariant-pin (bearer-cap behavior is response-code identical pre/post since both reject, but the test pins the cap regression-side).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(cloud-board): harden bearer auth (F#3 oracle, F#4 cache, F#10 doc, F#15 length cap)

GREEN: 6/6 group-1 regression tests now pass.

- F#3: require_admin returns uniform 401 (drops 403-on-valid-agent-token oracle)

- F#4: cache parsed agent-token env entries; malformed entries log+continue instead of 503ing every request

- F#10: document comma-in-token limitation

- F#15: refuse bearer tokens > 256 bytes before SHA-256 (DoS hardening)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test(cloud-board): RED — escrow double-pay + fail-closed regressions (F#1,2,5,7,8,9,12)

Pre-fix failure modes: 9 RED

- test_raise_dispute_rejects_terminal_escrow_no_double_payout: 200 != 400 (terminal escrow re-disputable, full create->release->dispute->resolve chain inflates total credits)

- test_disburse_disputed_escrow_refuses_second_payout: DID NOT RAISE (second disburse succeeds, doubling provider credits)

- test_scak_drift_score_rejects_non_finite_values[nan/inf/-inf]: DID NOT RAISE (validator absent on baseline)

- test_create_escrow_rejects_self_escrow: 200 != 400 (self-escrow accepted)

- test_create_escrow_rejects_unregistered_provider: 200 != 400 (no registration check)

- test_unauthorized_escrow_access_returns_404_not_403: 403 != 404 (oracle distinguishes participant vs non-participant)

- test_dispute_reason_capped_on_release_dispute: 200 != 422 (no length cap)

- 1 invariant-pin (release_dispute_branch_preserves_audit_reason) for F#7 defense-in-depth

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(cloud-board): close escrow double-pay + fail-closed validators (F#1,2,5,7,8,9,12)

GREEN: 10/10 group-2 regression tests now pass; full suite 32/32.

- F#1: raise_dispute refuses terminal states; idempotent already-disputed preserves reason; disburse_disputed_escrow rejects if resolved_at set (3 layered defenses)

- F#2: ReleaseEscrowRequest rejects NaN/+Inf/-Inf scak_drift_score via field_validator

- F#5: _authorize_escrow_participant returns 404 (not 403)

- F#7: release(outcome=dispute) preserves prior dispute_reason instead of clobbering with None

- F#8: ReleaseEscrowRequest.dispute_reason capped at 1000 chars

- F#9: create_escrow rejects requester_did == provider_did (SELF_ESCROW_FORBIDDEN)

- F#12: create_escrow rejects unregistered provider (PROVIDER_NOT_REGISTERED)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test(cloud-board): RED — arbiter dispute lifecycle regressions (F#5,6,8,14,17)

Pre-fix failure modes: 6 RED — 403!=404 oracle on dispute GET, 200!=409 on duplicate submit, KeyError submitted_by, 403!=404 oracle on submit, 200!=422 reason cap, orphan dispute not marked terminal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(cloud-board): tighten arbiter dispute lifecycle (F#5,6,8,14,17)

GREEN: 6/6 group-3 regression tests now pass.

- F#5: dispute participant checks return 404 (not 403)

- F#6: reject duplicate open disputes for same escrow (409 DISPUTE_ALREADY_OPEN)

- F#8: SubmitDisputeRequest.dispute_reason length-capped at 1000

- F#14: submit_dispute records submitted_by (agent DID or 'admin')

- F#17: resolve_dispute on missing escrow marks dispute terminal before 409

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test(cloud-board): RED — registry hardening regressions (F#3,11,16)

Pre-fix failure modes: 3 RED

- test_get_agent_redacts_pii_for_other_authenticated_callers: owner_id leaks to PROVIDER (non-owner authenticated caller) due to denylist redaction

- test_registration_rejects_naive_proof_timestamp: 500 TypeError 'can't subtract offset-naive and offset-aware datetimes' instead of 400

- test_did_now_uses_full_256_bit_sha256: full 64-char DID rejected as DID_MISMATCH because baseline truncates to 32 chars

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(cloud-board): registry hardening (F#3 PII allowlist, F#11 tz, F#16 256-bit DID)

GREEN: 3/3 group-4 regression tests now pass; full suite 39/39.

- F#3: _view_manifest uses an allowlist (did, verification_key, display_name); full identity only for owner or admin

- F#11: register/update_agent reject naive timestamps with 400 INVALID_TIMESTAMP

- F#16: derived DID uses full 64-hex-char SHA-256 (256-bit) instead of 128-bit truncation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(cloud-board): document reputation read asymmetry + PII redaction model (F#13)

Also fixes ruff W292 missing trailing newline in test_api_auth.py.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MohammadHaroonAbuomar added a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
Both reviewers (claude-opus-4.7-1m-internal + gpt-5.5) found
overlapping concerns. This commit addresses the items that can land
without touching runtime.rs.

Blockers fixed:
  - manifest.schema.json missing cedar branch (GPT microsoft#3). Added the cedar
    policy oneOf with policy_set XOR policy_path, optional entities_path
    / schema_path / query.
  - Evidence over 4 KiB silently accepted (GPT microsoft#2 partial). Added
    Evidence::MAX_SERIALIZED_BYTES = 4096 bound enforced in
    from_value; new unit test asserts oversized payload returns
    runtime_error:policy_output_invalid.

Warnings fixed:
  - Rust RuntimeError lacked the four resolution_* variants Python
    already exposes (GPT microsoft#4 / D6 cross-language parity). Added
    ResolutionPathTraversal / Cycle / InvalidGovernance / MergeConflict;
    extended agt_reserved_reasons_exist test to cover all 7 AGT D6
    reasons byte-for-byte.
  - agt-policies build.py silently dropped rules with unsupported
    operators (Opus microsoft#6). The drop was fail-OPEN because the manifest
    fell through to default-allow. Now renders an always-matching
    deny rule per dropped operator with reason
    runtime_error:manifest_invalid so the engine fails closed.
  - Decision::applies_effects() included Escalate (Opus microsoft#7). Spec §13.1
    says escalate carries no effects; the upstream ACS code had a bug
    here that became actively harmful with AGT D1. Removed Escalate;
    explicit Transform also returns false (uses verdict.transform
    instead). Parity fixture + test updated to match.
  - DELTA / AGT-SNAPSHOT documented the IFC library replacement as
    'MUST replace' the upstream file (Opus microsoft#5). Reframed as 'AGT ships
    agt_ifc.rego alongside upstream ifc.rego'; AGT users MUST import
    data.agt.ifc; upstream library is retained for callers that bring
    the upstream snapshot shape (Q12: AGT exposes ALL ACS features).

Remaining round-1 blockers (deferred to a focused follow-up):
  - Transform verdict parsed at normalization but NOT applied to the
    policy target at the engine level (Opus/GPT microsoft#1). Adding the
    application path requires changes to runtime.rs::evaluate_intervention_point.
  - Effects[] still accepted/applied by the engine (Opus microsoft#2). D1 says
    MUST reject. Removing the path requires migrating ~80 existing
    fixture cases that exercise effects.
  - Evidence telemetry propagation (Opus microsoft#3 / GPT remaining): the
    runtime needs to attach evidence_artefact and
    evidence_verification_pointer_keys to decision events, and emit
    intervention_point.transformed instead of effect_applied.
  - Bisected action identity (Opus microsoft#4 warning): runtime needs to
    compute input_identity AND enforced_identity for transform verdicts.

These four cluster around the same Rust file (runtime.rs +
telemetry.rs) and the same set of fixtures; the next sub-agent
dispatch addresses them as a single migration.

Test totals after this commit: pytest 44, cargo 170, opa 98 = 312.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
MohammadHaroonAbuomar added a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
… _bridge

Round-4 Opus regression: the previous round-4 fix (a27c118) incremented
``self._adapter_ctx.call_count`` AND called
``self._bridge.record_post_execute(tool_calls=1)``, but both mutations
ultimately advance the SnapshotBuilder's ``tool_call_count``:

- ``AdapterRuntimeBridge.builder_for(ctx)`` mirrors
  ``builder.tool_call_count = max(builder.tool_call_count, ctx.call_count)``
  on every call (`_v5_runtime_bridge.py:188`).
- ``record_post_execute(tool_calls=1)`` then adds another 1 to the same
  builder via ``record_tool_call``.

Result: after 3 sequential non-sensitive calls through the same
``_bridge``, the rego saw ``tool_call_count`` go 0 → 2 → 3 instead of
0 → 1 → 2. A ``GovernancePolicy(max_tool_calls=5)`` would deny on call
microsoft#4, not call microsoft#5 — a silent off-by-one against the documented
``max_tool_calls`` contract.

The smolagents adapter already warned about this anti-pattern
(`smolagents_adapter.py:734-738` / `:1032-1035` comments) and uses the
single-mutation pattern. The fix here adopts that pattern: drop both
``record_post_execute`` calls. The single ``ctx.call_count += 1``
propagates to both the default ``_bridge`` and the sibling
``_approval_bridge`` (when present) via the ``builder_for`` mirror,
which is what closed the original GPT round-3 budget-divergence
regression.

The new regression test
``test_repeated_non_sensitive_calls_do_not_double_count_budget``
asserts three sequential calls produce ``tool_call_count == [0, 1, 2]``
in the order the rego dispatcher observes. Test fails against pre-fix
code (observes ``[0, 2, 3]``), passes against post-fix.

Tested: 13/13 TestGoogleADKBridgeScenarios + 251 test_integrations.py
+ 10/10 demo. No regressions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Mohamed AbuOmar <mhabuomar@users.noreply.github.com>
MohammadHaroonAbuomar pushed a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
…xecute API (microsoft#2644)

* fix(agent-os): close authorization bypasses in stateless kernel and execute API

Three same-class authorization fixes identified in security review:

1. stateless._check_policies: caller-supplied params['approved']=True no longer satisfies requires_approval gates. Approval must flow through the trusted IntentManager path; unplanned drift on restricted actions is now denied. The legacy flag is stripped from params before action execution.

2. server/app.py /api/v1/execute: caller-supplied agent_id is no longer trusted when authentication is bypassed. The legacy AGENT_OS_ALLOW_UNAUTHENTICATED_EXECUTE env var now raises ValueError at construction time. The replacement AGENT_OS_UNSAFE_ALLOW_UNAUTHENTICATED_EXECUTE is gated on AGENT_OS_ENV in {dev,development,local}; the server-side identity is fixed by AGENT_OS_UNSAFE_LOCAL_EXECUTE_AGENT_ID (default local-dev-agent); mismatched caller agent_id is rejected with 422 (unsafe) or 403 (authenticated).

3. mcp-kernel-server KernelExecuteTool._check_policies: same params.get('approved') bypass pattern as (1); now ignored with a warning log and the action is denied with guidance pointing to a trusted host approval workflow.

Tests added/updated for all three paths. Tangential sweep covered other auth surfaces (mcp_gateway approval callback, AGENT_OS_* env vars, REST endpoints) and found no further in-class bugs in agent-os core; module-level FastAPI surfaces in caas/iatp/observability are out of scope for this PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(mcp-scan): regression for env-poisoning RCE + cwd hijack -- currently FAILING

Red-team findings microsoft#1 + microsoft#2: mcp-scan CLI accepts arbitrary environment keys (LD_PRELOAD, PYTHONPATH, NODE_OPTIONS, ...) and untrusted cwd paths when launching subprocesses, enabling pre-exec code injection.

These regression tests assert the SECURE behavior (refusal). They FAIL on this commit because the helpers _blocked_command_env_keys and _validate_launch_cwd do not exist, proving the vuln surface is present.

Failure mode: 28 errors in TestLaunchEnvAndCwdGuards (AttributeError on missing helpers). Fix applied in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(mcp-scan): restore env-key blocklist and untrusted-cwd guard

Closes red-team findings microsoft#1 + microsoft#2. Restores _blocked_command_env_keys and _validate_launch_cwd helpers. Red->Green: 28 errors -> 129 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(authz): regression for approval-key bypasses + provider edge cases -- currently FAILING

Red-team findings microsoft#8 (confusable/nested approved keys bypass strip), microsoft#10 (non-strict-True provider return treated as allow), microsoft#11 (log injection via CR/LF in caller fields), microsoft#12 (provider BaseException leaks past approval check).

Failure mode: 15 failures across stateless + mcp_kernel_server.tools. Cyrillic 'approvеd', uppercased 'Approved', nested dict values, truthy-non-bool returns ('yes', 1, object), and SystemExit/KeyboardInterrupt all currently bypass the gate. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(authz): harden approval-key strip, strict-bool, BaseException, log sanitization

Closes red-team microsoft#8, microsoft#10, microsoft#11, microsoft#12. NFKC + casefold approved-key match, recursive strip into nested dicts/lists, strict 'is True', except BaseException, _sanitize_log_field. Red->Green: 15 failed -> 141 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(authz): regression for empty-policies bypass + non-loopback execute -- currently FAILING

Red-team findings microsoft#3 (no policy match -> action allowed even when requires_approval declared elsewhere) and microsoft#5 (unsafe execute mode trusted from arbitrary remote peers).

Failure mode: test_execute_global_approval_blocks_empty_policy_list FAILS because StatelessKernel falls through to allow when no policy entry matches. test_execute_unsafe_escape_hatch_rejects_non_loopback_peer FAILS because _authenticate_execute_request does not inspect request.client. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(authz): close empty-policies bypass and enforce loopback for unsafe execute

Closes microsoft#3 + microsoft#5. _globally_protected_actions enforced after per-policy loop; _is_loopback_client rejects non-127.x/::1 peers with 403. Red->Green: 2 failed -> 94 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(intent): regression for cross-agent intent reuse -- currently FAILING

Red-team finding microsoft#4: IntentManager.check_action does not verify that the caller's agent_id matches the intent's agent_id, so agent B can reuse agent A's stored intent record to perform privileged actions under A's policy context.

Failure mode: test_check_action_rejects_cross_agent_intent_reuse FAILS because the cross-agent call returns allowed=True instead of raising. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(intent): bind intent to declaring agent_id

Closes microsoft#4. Asserts intent.agent_id == caller agent_id in check_action. Red->Green: 1 failed -> 41 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(iatp): regression for weak/short trusted-override tokens -- currently FAILING

Red-team finding microsoft#9: AGENT_OS_IATP_TRUSTED_OVERRIDE_TOKEN accepts any non-empty string -- 'true', 'admin', 'password', 'x' -- so a misconfigured operator (or attacker who can set one env var) trivially enables the X-User-Override path.

Failure mode: 18 failures in test_blacklisted_weak_token_disables_gate (main+sidecar paths) and test_short_token_disables_gate. Each demonstrates a weak/short token still bypassing the override check. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(iatp): reject weak/short trusted-override tokens

Closes microsoft#9. _load_trusted_override_token enforces 16-char minimum and blacklists {true,yes,admin,password,...}. Sidecar delegates to iatp.main to prevent drift. Red->Green: 18 failed -> 30 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(policies): regression for plaintext OPA over network -- currently FAILING

Red-team finding microsoft#7: OPABackend remote mode follows http:// URLs to non-loopback hosts without warning. An on-path attacker on the OPA route flips allow=true and the kernel approves any action.

Failure mode: test_plaintext_remote_non_loopback_denied and test_plaintext_opt_in_without_local_env_denied FAIL because _evaluate_remote performs the HTTP call without protocol gating. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(policies): require HTTPS for remote OPA unless explicitly opted in

Closes microsoft#7. _evaluate_remote rejects non-HTTPS unless loopback host OR (AGENT_OS_OPA_ALLOW_PLAINTEXT=1 + AGENT_OS_ENV in {local,dev,development}). Plaintext non-loopback returns error='plaintext_opa_blocked'. Red->Green: 2 failed -> 77 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(caas): regression for unauthenticated FastAPI surface gate -- currently FAILING

Red-team finding microsoft#6: caas.api.server only LOGS a warning when started outside local env; misconfigured deployment exposes every CaaS route silently.

Failure mode: 13 failures because _caas_unauth_gate_satisfied does not exist and startup hook does not raise. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(caas): require explicit env gate to start unauthenticated CaaS surface

Closes microsoft#6. Startup hook raises RuntimeError unless AGENT_OS_ENV in {local,dev,development} OR CAAS_UNSAFE_ALLOW_UNAUTH=1. Red->Green: 13 failed -> 13 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* ci(agent-os): clear no-stubs/no-crypto/spell-check/safety-critical CI gates

- Reword TODO(security) doc comments to 'Future hardening (security)' in caas/api/server.py, iatp/main.py (x2 including proxy_task cross-ref), iatp/sidecar/__init__.py so the no-stubs CI gate accepts the docs without losing the design-followup intent.

- Replace inline 'import hmac; hmac.compare_digest' with 'import secrets; secrets.compare_digest' in iatp/main.py so the no-custom-crypto CI gate is happy (secrets.compare_digest is the stdlib re-export of hmac.compare_digest, same constant-time guarantee).

- Add 19 project-specific terms to .cspell-repo-terms.txt (ASGI, NFKC, casefold, confusables, multitenant, normalisation, sanitised, unicodedata, testclient, monkeypatched, baseexception, rsplit, hdrs, oncall, madmin, backendunavailable, changeme, shortone, approv) for the spell-check-changed-files job.

- Update tests/test_safety_critical.py::TestPolicyEdgeCases::test_empty_policies_list_allows to reflect the new fail-closed behavior from fix microsoft#3: an empty policies list must DENY requires_approval actions (file_write). Renamed to test_empty_policies_list_denies_protected_actions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* ci(spell-check): allow cyrillic-e 'approv\u0435d' confusable used in unicode normalization tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

---------

Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
DhineshPonnarasan pushed a commit to DhineshPonnarasan/agent-governance-toolkit that referenced this pull request Jun 1, 2026
…outes (microsoft#2645)

* fix(cloud-board): add bearer auth, close credit-minting gap, harden routes

Adds a fail-closed bearer-token auth layer to the Nexus Cloud Board API
and resolves issues surfaced in the recent security review:

- New api/auth.py with admin and agent-scoped principals, SHA-256 +
  hmac.compare_digest token comparison, '<did>=<token>' agent token
  entries, 401 with WWW-Authenticate, and 503 when tokens are not
  configured.
- Registry: registration binds the request DID to the verification key,
  PUT enforces auth + proof-of-possession + DID match, DELETE requires
  scoped auth, GET/discover redact owner_id and contact for anonymous
  callers.
- Reputation: report and slash are admin-only; slash history is
  admin-only because it exposes evidence and trace_ids.
- Escrow: all mutating endpoints require auth, credits start at 0 (no
  self-minting), add_credits is admin-only and rejects non-positive
  amounts, raise_dispute now uses a JSON body.
- Arbiter: disputes require an existing escrow, bind the disputing party
  to the authenticated principal, store participant DIDs, restrict
  resolution to admins, and scope reads to participants.
- Compliance: events/stats/export/download/data-handling are admin-only.
- Route ordering fix: /discover, /sync, /leaderboard, /slashes were
  shadowed by /{agent_did} path-param routes.
- README documents env vars, deliberately public reads, and the
  demo-only security boundary.
- 14 pytest cases under tests/cloud_board/test_api_auth.py.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(cloud-board): close SCAK fail-open, require admin outcome on resolve

Addresses Opus review findings on PR microsoft#2645:

- Escrow release with require_scak=true no longer succeeds when
  scak_drift_score is omitted. Missing drift score now returns 400
  SCAK_DRIFT_SCORE_REQUIRED instead of falling through to the success
  path. Drift above the threshold still resolves as failure.
- Arbiter resolve_dispute now requires an admin-supplied outcome
  (requester_wins | provider_wins | split) plus optional explanation.
  The arbiter no longer derives the winner from claimed_outcome (which
  is supplied by the disputing party at submit time and is therefore
  attacker-influenced).
- Arbiter get_resolution now returns the resolution record actually
  stored by resolve_dispute. It 404s with RESOLUTION_NOT_FOUND before
  the dispute is resolved, instead of returning a hardcoded 50/50
  split with a fabricated explanation.
- Three regression tests added (now 17 total): SCAK release without
  drift score is rejected; resolve_dispute without/with bad outcome is
  rejected and admin outcome is recorded; get_resolution 404s before
  resolve and returns the stored outcome after.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(cloud-board): wire arbiter to escrow state machine, tighten release auth

Addresses Opus PR microsoft#2645 re-review finding microsoft#4 ("resolve_dispute is security
theater") and a tangential sweep finding (submit_dispute did not lock the
escrow against further releases).

Changes:

- release_escrow: outcome="failure" now requires the provider's token
  (or admin), not the requester's. A requester cannot unilaterally
  refund themselves by claiming failure; the dispute flow is the only
  way to contest a delivery. outcome="success" still requires the
  requester (acknowledging delivery) and outcome="dispute" requires
  either participant.
- submit_dispute (arbiter): now atomically marks the escrow as
  "disputed" via a new escrow.mark_escrow_disputed helper. Once a
  dispute is open, neither party can /release the escrow until the
  arbiter rules. Idempotent for already-disputed escrows; rejects
  terminal-state escrows with 400 ESCROW_ALREADY_RESOLVED.
- resolve_dispute (arbiter): no longer returns a fabricated 100-credit
  payout that never moves state. It now (a) looks up the escrow's
  actual locked credit total via escrow.get_escrow_credits, (b)
  computes the split, (c) calls escrow.disburse_disputed_escrow to
  actually move the credits and transition the escrow out of
  "disputed", and (d) emits a "dispute_resolved" compliance event.
  Reputation deltas remain advisory (documented in README) since real
  reputation wiring is out of scope.
- escrow: new helpers get_escrow_credits, mark_escrow_disputed,
  disburse_disputed_escrow. The disburse helper rejects splits that do
  not sum to the locked credit total (400 DISBURSEMENT_MISMATCH) so
  arbiter math errors fail loudly.

README: documents the per-outcome release auth model, the dispute
locking guarantee, and the reputation-still-advisory boundary.

Tests: 20/20 passing (3 new):
- test_release_outcome_failure_requires_provider_or_admin
- test_submit_dispute_locks_escrow_against_subsequent_release
- test_resolve_dispute_disburses_locked_credits_and_unlocks_escrow

GPT-5.5 re-review was clean (no blockers/warnings).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(cloud-board): RED — bearer-auth oracle + env-cache regressions (F#3,4,10,15)

Pre-fix failure modes: 5 RED (403 vs 401 oracle on require_admin x4 endpoints; 503 vs 200 on admin plane when one env entry is malformed); 1 invariant-pin (bearer-cap behavior is response-code identical pre/post since both reject, but the test pins the cap regression-side).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(cloud-board): harden bearer auth (F#3 oracle, F#4 cache, F#10 doc, F#15 length cap)

GREEN: 6/6 group-1 regression tests now pass.

- F#3: require_admin returns uniform 401 (drops 403-on-valid-agent-token oracle)

- F#4: cache parsed agent-token env entries; malformed entries log+continue instead of 503ing every request

- F#10: document comma-in-token limitation

- F#15: refuse bearer tokens > 256 bytes before SHA-256 (DoS hardening)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test(cloud-board): RED — escrow double-pay + fail-closed regressions (F#1,2,5,7,8,9,12)

Pre-fix failure modes: 9 RED

- test_raise_dispute_rejects_terminal_escrow_no_double_payout: 200 != 400 (terminal escrow re-disputable, full create->release->dispute->resolve chain inflates total credits)

- test_disburse_disputed_escrow_refuses_second_payout: DID NOT RAISE (second disburse succeeds, doubling provider credits)

- test_scak_drift_score_rejects_non_finite_values[nan/inf/-inf]: DID NOT RAISE (validator absent on baseline)

- test_create_escrow_rejects_self_escrow: 200 != 400 (self-escrow accepted)

- test_create_escrow_rejects_unregistered_provider: 200 != 400 (no registration check)

- test_unauthorized_escrow_access_returns_404_not_403: 403 != 404 (oracle distinguishes participant vs non-participant)

- test_dispute_reason_capped_on_release_dispute: 200 != 422 (no length cap)

- 1 invariant-pin (release_dispute_branch_preserves_audit_reason) for F#7 defense-in-depth

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(cloud-board): close escrow double-pay + fail-closed validators (F#1,2,5,7,8,9,12)

GREEN: 10/10 group-2 regression tests now pass; full suite 32/32.

- F#1: raise_dispute refuses terminal states; idempotent already-disputed preserves reason; disburse_disputed_escrow rejects if resolved_at set (3 layered defenses)

- F#2: ReleaseEscrowRequest rejects NaN/+Inf/-Inf scak_drift_score via field_validator

- F#5: _authorize_escrow_participant returns 404 (not 403)

- F#7: release(outcome=dispute) preserves prior dispute_reason instead of clobbering with None

- F#8: ReleaseEscrowRequest.dispute_reason capped at 1000 chars

- F#9: create_escrow rejects requester_did == provider_did (SELF_ESCROW_FORBIDDEN)

- F#12: create_escrow rejects unregistered provider (PROVIDER_NOT_REGISTERED)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test(cloud-board): RED — arbiter dispute lifecycle regressions (F#5,6,8,14,17)

Pre-fix failure modes: 6 RED — 403!=404 oracle on dispute GET, 200!=409 on duplicate submit, KeyError submitted_by, 403!=404 oracle on submit, 200!=422 reason cap, orphan dispute not marked terminal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(cloud-board): tighten arbiter dispute lifecycle (F#5,6,8,14,17)

GREEN: 6/6 group-3 regression tests now pass.

- F#5: dispute participant checks return 404 (not 403)

- F#6: reject duplicate open disputes for same escrow (409 DISPUTE_ALREADY_OPEN)

- F#8: SubmitDisputeRequest.dispute_reason length-capped at 1000

- F#14: submit_dispute records submitted_by (agent DID or 'admin')

- F#17: resolve_dispute on missing escrow marks dispute terminal before 409

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test(cloud-board): RED — registry hardening regressions (F#3,11,16)

Pre-fix failure modes: 3 RED

- test_get_agent_redacts_pii_for_other_authenticated_callers: owner_id leaks to PROVIDER (non-owner authenticated caller) due to denylist redaction

- test_registration_rejects_naive_proof_timestamp: 500 TypeError 'can't subtract offset-naive and offset-aware datetimes' instead of 400

- test_did_now_uses_full_256_bit_sha256: full 64-char DID rejected as DID_MISMATCH because baseline truncates to 32 chars

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(cloud-board): registry hardening (F#3 PII allowlist, F#11 tz, F#16 256-bit DID)

GREEN: 3/3 group-4 regression tests now pass; full suite 39/39.

- F#3: _view_manifest uses an allowlist (did, verification_key, display_name); full identity only for owner or admin

- F#11: register/update_agent reject naive timestamps with 400 INVALID_TIMESTAMP

- F#16: derived DID uses full 64-hex-char SHA-256 (256-bit) instead of 128-bit truncation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(cloud-board): document reputation read asymmetry + PII redaction model (F#13)

Also fixes ruff W292 missing trailing newline in test_api_auth.py.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
DhineshPonnarasan pushed a commit to DhineshPonnarasan/agent-governance-toolkit that referenced this pull request Jun 1, 2026
…xecute API (microsoft#2644)

* fix(agent-os): close authorization bypasses in stateless kernel and execute API

Three same-class authorization fixes identified in security review:

1. stateless._check_policies: caller-supplied params['approved']=True no longer satisfies requires_approval gates. Approval must flow through the trusted IntentManager path; unplanned drift on restricted actions is now denied. The legacy flag is stripped from params before action execution.

2. server/app.py /api/v1/execute: caller-supplied agent_id is no longer trusted when authentication is bypassed. The legacy AGENT_OS_ALLOW_UNAUTHENTICATED_EXECUTE env var now raises ValueError at construction time. The replacement AGENT_OS_UNSAFE_ALLOW_UNAUTHENTICATED_EXECUTE is gated on AGENT_OS_ENV in {dev,development,local}; the server-side identity is fixed by AGENT_OS_UNSAFE_LOCAL_EXECUTE_AGENT_ID (default local-dev-agent); mismatched caller agent_id is rejected with 422 (unsafe) or 403 (authenticated).

3. mcp-kernel-server KernelExecuteTool._check_policies: same params.get('approved') bypass pattern as (1); now ignored with a warning log and the action is denied with guidance pointing to a trusted host approval workflow.

Tests added/updated for all three paths. Tangential sweep covered other auth surfaces (mcp_gateway approval callback, AGENT_OS_* env vars, REST endpoints) and found no further in-class bugs in agent-os core; module-level FastAPI surfaces in caas/iatp/observability are out of scope for this PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(mcp-scan): regression for env-poisoning RCE + cwd hijack -- currently FAILING

Red-team findings #1 + microsoft#2: mcp-scan CLI accepts arbitrary environment keys (LD_PRELOAD, PYTHONPATH, NODE_OPTIONS, ...) and untrusted cwd paths when launching subprocesses, enabling pre-exec code injection.

These regression tests assert the SECURE behavior (refusal). They FAIL on this commit because the helpers _blocked_command_env_keys and _validate_launch_cwd do not exist, proving the vuln surface is present.

Failure mode: 28 errors in TestLaunchEnvAndCwdGuards (AttributeError on missing helpers). Fix applied in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(mcp-scan): restore env-key blocklist and untrusted-cwd guard

Closes red-team findings #1 + microsoft#2. Restores _blocked_command_env_keys and _validate_launch_cwd helpers. Red->Green: 28 errors -> 129 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(authz): regression for approval-key bypasses + provider edge cases -- currently FAILING

Red-team findings microsoft#8 (confusable/nested approved keys bypass strip), microsoft#10 (non-strict-True provider return treated as allow), microsoft#11 (log injection via CR/LF in caller fields), microsoft#12 (provider BaseException leaks past approval check).

Failure mode: 15 failures across stateless + mcp_kernel_server.tools. Cyrillic 'approvеd', uppercased 'Approved', nested dict values, truthy-non-bool returns ('yes', 1, object), and SystemExit/KeyboardInterrupt all currently bypass the gate. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(authz): harden approval-key strip, strict-bool, BaseException, log sanitization

Closes red-team microsoft#8, microsoft#10, microsoft#11, microsoft#12. NFKC + casefold approved-key match, recursive strip into nested dicts/lists, strict 'is True', except BaseException, _sanitize_log_field. Red->Green: 15 failed -> 141 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(authz): regression for empty-policies bypass + non-loopback execute -- currently FAILING

Red-team findings microsoft#3 (no policy match -> action allowed even when requires_approval declared elsewhere) and microsoft#5 (unsafe execute mode trusted from arbitrary remote peers).

Failure mode: test_execute_global_approval_blocks_empty_policy_list FAILS because StatelessKernel falls through to allow when no policy entry matches. test_execute_unsafe_escape_hatch_rejects_non_loopback_peer FAILS because _authenticate_execute_request does not inspect request.client. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(authz): close empty-policies bypass and enforce loopback for unsafe execute

Closes microsoft#3 + microsoft#5. _globally_protected_actions enforced after per-policy loop; _is_loopback_client rejects non-127.x/::1 peers with 403. Red->Green: 2 failed -> 94 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(intent): regression for cross-agent intent reuse -- currently FAILING

Red-team finding microsoft#4: IntentManager.check_action does not verify that the caller's agent_id matches the intent's agent_id, so agent B can reuse agent A's stored intent record to perform privileged actions under A's policy context.

Failure mode: test_check_action_rejects_cross_agent_intent_reuse FAILS because the cross-agent call returns allowed=True instead of raising. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(intent): bind intent to declaring agent_id

Closes microsoft#4. Asserts intent.agent_id == caller agent_id in check_action. Red->Green: 1 failed -> 41 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(iatp): regression for weak/short trusted-override tokens -- currently FAILING

Red-team finding microsoft#9: AGENT_OS_IATP_TRUSTED_OVERRIDE_TOKEN accepts any non-empty string -- 'true', 'admin', 'password', 'x' -- so a misconfigured operator (or attacker who can set one env var) trivially enables the X-User-Override path.

Failure mode: 18 failures in test_blacklisted_weak_token_disables_gate (main+sidecar paths) and test_short_token_disables_gate. Each demonstrates a weak/short token still bypassing the override check. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(iatp): reject weak/short trusted-override tokens

Closes microsoft#9. _load_trusted_override_token enforces 16-char minimum and blacklists {true,yes,admin,password,...}. Sidecar delegates to iatp.main to prevent drift. Red->Green: 18 failed -> 30 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(policies): regression for plaintext OPA over network -- currently FAILING

Red-team finding microsoft#7: OPABackend remote mode follows http:// URLs to non-loopback hosts without warning. An on-path attacker on the OPA route flips allow=true and the kernel approves any action.

Failure mode: test_plaintext_remote_non_loopback_denied and test_plaintext_opt_in_without_local_env_denied FAIL because _evaluate_remote performs the HTTP call without protocol gating. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(policies): require HTTPS for remote OPA unless explicitly opted in

Closes microsoft#7. _evaluate_remote rejects non-HTTPS unless loopback host OR (AGENT_OS_OPA_ALLOW_PLAINTEXT=1 + AGENT_OS_ENV in {local,dev,development}). Plaintext non-loopback returns error='plaintext_opa_blocked'. Red->Green: 2 failed -> 77 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* test(caas): regression for unauthenticated FastAPI surface gate -- currently FAILING

Red-team finding microsoft#6: caas.api.server only LOGS a warning when started outside local env; misconfigured deployment exposes every CaaS route silently.

Failure mode: 13 failures because _caas_unauth_gate_satisfied does not exist and startup hook does not raise. Fix in next commit.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* fix(caas): require explicit env gate to start unauthenticated CaaS surface

Closes microsoft#6. Startup hook raises RuntimeError unless AGENT_OS_ENV in {local,dev,development} OR CAAS_UNSAFE_ALLOW_UNAUTH=1. Red->Green: 13 failed -> 13 passed.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* ci(agent-os): clear no-stubs/no-crypto/spell-check/safety-critical CI gates

- Reword TODO(security) doc comments to 'Future hardening (security)' in caas/api/server.py, iatp/main.py (x2 including proxy_task cross-ref), iatp/sidecar/__init__.py so the no-stubs CI gate accepts the docs without losing the design-followup intent.

- Replace inline 'import hmac; hmac.compare_digest' with 'import secrets; secrets.compare_digest' in iatp/main.py so the no-custom-crypto CI gate is happy (secrets.compare_digest is the stdlib re-export of hmac.compare_digest, same constant-time guarantee).

- Add 19 project-specific terms to .cspell-repo-terms.txt (ASGI, NFKC, casefold, confusables, multitenant, normalisation, sanitised, unicodedata, testclient, monkeypatched, baseexception, rsplit, hdrs, oncall, madmin, backendunavailable, changeme, shortone, approv) for the spell-check-changed-files job.

- Update tests/test_safety_critical.py::TestPolicyEdgeCases::test_empty_policies_list_allows to reflect the new fail-closed behavior from fix microsoft#3: an empty policies list must DENY requires_approval actions (file_write). Renamed to test_empty_policies_list_denies_protected_actions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

* ci(spell-check): allow cyrillic-e 'approv\u0435d' confusable used in unicode normalization tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

---------

Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
MohammadHaroonAbuomar added a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
Both reviewers (claude-opus-4.7-1m-internal + gpt-5.5) found
overlapping concerns. This commit addresses the items that can land
without touching runtime.rs.

Blockers fixed:
  - manifest.schema.json missing cedar branch (GPT microsoft#3). Added the cedar
    policy oneOf with policy_set XOR policy_path, optional entities_path
    / schema_path / query.
  - Evidence over 4 KiB silently accepted (GPT microsoft#2 partial). Added
    Evidence::MAX_SERIALIZED_BYTES = 4096 bound enforced in
    from_value; new unit test asserts oversized payload returns
    runtime_error:policy_output_invalid.

Warnings fixed:
  - Rust RuntimeError lacked the four resolution_* variants Python
    already exposes (GPT microsoft#4 / D6 cross-language parity). Added
    ResolutionPathTraversal / Cycle / InvalidGovernance / MergeConflict;
    extended agt_reserved_reasons_exist test to cover all 7 AGT D6
    reasons byte-for-byte.
  - agt-policies build.py silently dropped rules with unsupported
    operators (Opus microsoft#6). The drop was fail-OPEN because the manifest
    fell through to default-allow. Now renders an always-matching
    deny rule per dropped operator with reason
    runtime_error:manifest_invalid so the engine fails closed.
  - Decision::applies_effects() included Escalate (Opus microsoft#7). Spec §13.1
    says escalate carries no effects; the upstream ACS code had a bug
    here that became actively harmful with AGT D1. Removed Escalate;
    explicit Transform also returns false (uses verdict.transform
    instead). Parity fixture + test updated to match.
  - DELTA / AGT-SNAPSHOT documented the IFC library replacement as
    'MUST replace' the upstream file (Opus microsoft#5). Reframed as 'AGT ships
    agt_ifc.rego alongside upstream ifc.rego'; AGT users MUST import
    data.agt.ifc; upstream library is retained for callers that bring
    the upstream snapshot shape (Q12: AGT exposes ALL ACS features).

Remaining round-1 blockers (deferred to a focused follow-up):
  - Transform verdict parsed at normalization but NOT applied to the
    policy target at the engine level (Opus/GPT microsoft#1). Adding the
    application path requires changes to runtime.rs::evaluate_intervention_point.
  - Effects[] still accepted/applied by the engine (Opus microsoft#2). D1 says
    MUST reject. Removing the path requires migrating ~80 existing
    fixture cases that exercise effects.
  - Evidence telemetry propagation (Opus microsoft#3 / GPT remaining): the
    runtime needs to attach evidence_artefact and
    evidence_verification_pointer_keys to decision events, and emit
    intervention_point.transformed instead of effect_applied.
  - Bisected action identity (Opus microsoft#4 warning): runtime needs to
    compute input_identity AND enforced_identity for transform verdicts.

These four cluster around the same Rust file (runtime.rs +
telemetry.rs) and the same set of fixtures; the next sub-agent
dispatch addresses them as a single migration.

Test totals after this commit: pytest 44, cargo 170, opa 98 = 312.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
MohammadHaroonAbuomar added a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
… _bridge

Round-4 Opus regression: the previous round-4 fix (2604ea0) incremented
``self._adapter_ctx.call_count`` AND called
``self._bridge.record_post_execute(tool_calls=1)``, but both mutations
ultimately advance the SnapshotBuilder's ``tool_call_count``:

- ``AdapterRuntimeBridge.builder_for(ctx)`` mirrors
  ``builder.tool_call_count = max(builder.tool_call_count, ctx.call_count)``
  on every call (`_v5_runtime_bridge.py:188`).
- ``record_post_execute(tool_calls=1)`` then adds another 1 to the same
  builder via ``record_tool_call``.

Result: after 3 sequential non-sensitive calls through the same
``_bridge``, the rego saw ``tool_call_count`` go 0 → 2 → 3 instead of
0 → 1 → 2. A ``GovernancePolicy(max_tool_calls=5)`` would deny on call
microsoft#4, not call microsoft#5 — a silent off-by-one against the documented
``max_tool_calls`` contract.

The smolagents adapter already warned about this anti-pattern
(`smolagents_adapter.py:734-738` / `:1032-1035` comments) and uses the
single-mutation pattern. The fix here adopts that pattern: drop both
``record_post_execute`` calls. The single ``ctx.call_count += 1``
propagates to both the default ``_bridge`` and the sibling
``_approval_bridge`` (when present) via the ``builder_for`` mirror,
which is what closed the original GPT round-3 budget-divergence
regression.

The new regression test
``test_repeated_non_sensitive_calls_do_not_double_count_budget``
asserts three sequential calls produce ``tool_call_count == [0, 1, 2]``
in the order the rego dispatcher observes. Test fails against pre-fix
code (observes ``[0, 2, 3]``), passes against post-fix.

Tested: 13/13 TestGoogleADKBridgeScenarios + 251 test_integrations.py
+ 10/10 demo. No regressions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Mohamed AbuOmar <mhabuomar@users.noreply.github.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
MohammadHaroonAbuomar added a commit that referenced this pull request Jun 2, 2026
* feat(policy-engine): vendor ACS as AGT 5.0 policy layer

Vendors responsibleai/AgentControlSpecification@318dbca into the new
policy-engine/ directory. ACS becomes the AGT-owned policy engine per
the AGT 5.0 redesign documented in architecture-exploration.md.

Headline divergences from upstream ACS (to be implemented in M1-M2):
- Effects removed from verdict; transform verdict type introduced (Q2).
- Optional evidence field on verdict + telemetry events (Q4).
- Cedar promoted to a built-in policy type (Q10).
- approval top-level manifest section (Q13).
- AGT folder discovery, scope filter, and merge layer pre-resolves
  manifests before they reach this engine; engine never sees extends
  from an AGT host (Q6).

Original ACS LICENSE preserved at policy-engine/LICENSE.acs.
Original ACS README preserved at policy-engine/README.vendored-acs.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* spec(policy-engine): add AGT divergence + manifest/resolution/snapshot/evidence specs

5 normative spec docs implementing user decisions Q2-Q14:

  - SPECIFICATION-AGT-DELTA.md: section-by-section deltas from upstream ACS
    spec — drop effects, add transform verdict, add evidence field, promote
    Cedar to built-in policy type, add approval section, add reserved reasons,
    document cargo feature split.

  - agt/AGT-MANIFEST-1.0.md: full manifest surface AGT hosts author including
    new top-level approval and limits sections.

  - agt/AGT-RESOLUTION-1.0.md: AGT-side folder discovery + scope filtering +
    merge layer that pre-resolves manifest chains before the engine sees them
    (preserves AGT v4 folder discovery while keeping ACS engine simple).

  - agt/AGT-SNAPSHOT-1.0.md: per-intervention-point snapshot shape so
    AGT-authored Rego/Cedar rules are portable across SDKs.

  - agt/AGT-EVIDENCE-1.0.md: proof_artefact + verification_pointers
    convention for high-assurance dispatchers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* spec(policy-engine): round-1 fixes from M1 multi-model review

Synthesized fixes addressing 5 unique blockers and 5 warnings from the
multi-model review by claude-opus-4.7-1m-internal and gpt-5.5.

Blockers:
  1. (Opus) restore result_labels to D1 verdict members; IFC propagation
     was silently dropped.
  2. (Opus) renumber approval to §24 to avoid colliding with upstream §22
     Versioning and §23 References; patch summary-of-impacts.
  3. (Opus) AGT-RESOLUTION emitted policy_set on a type:rego policy; rego
     only accepts bundle. Rewrote §2.5 to materialize a Rego bundle on
     disk and bind type:rego with bundle path.
  4. (GPT)  AGT-RESOLUTION path traversal returned an empty manifest that
     evaluates to allow; replaced with fail-closed
     runtime_error:resolution_path_traversal. §5 empty-manifest fallback
     removed; missing governance now MUST fail closed or substitute a
     host-registered default.
  5. (GPT)  D1.4 action identity bound only to pre-transform input; auditor
     could not replay the executed action. Bisected into input_identity
     and enforced_identity; approval binding moves to enforced_identity.

Warnings:
  - Cedar default mapping aligned to envelope.agent.id per AGT-SNAPSHOT §1
    (was snapshot.agent.id).
  - Telemetry event names standardized on upstream
    intervention_point.{allowed,denied,warned,escalated} plus the new
    intervention_point.transformed; removed invented intervention_point.decided.
  - Six new runtime_error:resolution_* reasons added to D6.
  - Cedar advice schema specified in D3.3.
  - AGT-SNAPSHOT §2.2 clarifies IFC paths are input.ifc.* and
    response.ifc.*, not snapshot.ifc.*.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/core): add Transform decision, Evidence verdict field, AGT reserved reasons

M2.S1 and M2.S3 from plan v3. Implements SPECIFICATION-AGT-DELTA D1 and D2
without removing the upstream effects path (kept for parity through M2.S5
when the workspace split lands and effects can be feature-gated off).

verdict.rs:
  - Decision::Transform variant added; permits() helper bisects allow/warn/
    transform from deny/escalate.
  - Transform struct parses {path, value}; rejects transforms whose path is
    not rooted at  (TransformTargetForbidden) or whose path
    fails JsonPath parse.
  - Evidence struct parses {artefact, verification_pointers}; sorted
    pointer_keys() helper for telemetry per AGT-EVIDENCE-1.0 §3.
  - normalize_policy_output rejects transform on non-transform decisions and
    transform decisions without a body.
  - 12 new unit tests; lib suite 43 → 55.

error.rs:
  - 3 new variants for D6 reserved reasons:
      TransformTargetForbidden -> runtime_error:transform_target_forbidden
      TransformInvalid         -> runtime_error:transform_invalid
      ApprovalResolverMissing  -> runtime_error:approval_resolver_missing
  - reason() and detail() updated; AGENTS.md house style preserved.

Test suite: 130 tests pass, 0 failures (was 118).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* spec(policy-engine): round-2 consensus warnings from M1 multi-model review

Round-2 review by claude-opus-4.7-1m-internal and gpt-5.5 reached
consensus: 0 blockers, no disagreements. Both reviewers raised 2
warnings each, all converging on these 4 fixes.

  - DELTA summary-of-impacts §5 Modes was 'Unchanged' but D1's effects
    removal changes evaluate_only validation semantics. Now describes the
    transform-shaped validation.
  - DELTA §11 IFC was 'Unchanged' but AGT-SNAPSHOT diverges from upstream
    on the path (input.ifc.* vs input.snapshot.ifc.*). Now flagged as
    path-clarified and the upstream policy/lib/ifc.rego replacement is
    called out so M4 doesn't ship a fail-closed-on-every-call default.
  - DELTA §19 omitted the removal of intervention_point.effect_applied.
    Now stated explicitly and points consumers at intervention_point.transformed.
  - AGT-EVIDENCE §4 used SHOULD store while DELTA D1.4 used MUST be in
    every audit record. Tightened to MUST.

No code changes; spec docs only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/core): parse AGT D5 top-level approval section (M2.S4)

Add ApprovalSection, ApprovalResolverConfig, and ApprovalOnTimeout types
to the vendored ACS manifest parser. The new field on Manifest is
optional and backwards compatible; manifests without an `approval` block
continue to parse and validate as before.

The Manifest::approval() accessor exposes the parsed section. The
runtime treats resolver configuration as opaque per
SPECIFICATION-AGT-DELTA D5; only the section shape is validated.

Validation rules per D5:
  - on_timeout must be one of deny|allow|suspend
  - default_resolver must match a key in resolvers when both are set
  - timeout_seconds, fatigue_threshold, fatigue_window_seconds when
    present must be > 0
  - bad shapes fail closed with runtime_error:manifest_invalid

10 unit tests cover all five validation rules plus three positive
parse-and-round-trip cases. Test suite grows from 130 to 140 passing.

This commit was originally landed with the wrong subject line during a
worktree coordination overlap; the reword corrects the history without
changing any file content.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/core): add cedar PolicyConfig variant per AGT D3.1

Promote Cedar to a built-in policy type alongside rego, test, and custom,
implementing the manifest-side surface from AGT M2.S2 per
`policy-engine/spec/SPECIFICATION-AGT-DELTA.md` §D3.1.

The new `PolicyConfig::Cedar(CedarPolicyConfig)` variant accepts the
fields fixed by D3.1: `policy_set` xor `policy_path` (exactly one
required), optional `entities_path`, optional `schema_path`, and an
optional `query` object whose shape is open for AGT v5. Unknown fields
are rejected via `serde(deny_unknown_fields)` so a manifest that mixes
rego-shaped fields (e.g. `bundle`) into a cedar policy is caught at
deserialization. Relative cedar paths resolve against the declaring
manifest's directory in `resolve_relative_paths`, matching the
rego.bundle behaviour.

`validate_policy_definition` enforces the cross-type strictness: a
`rego` policy that carries any of the reserved cedar field names
(`policy_set`, `policy_path`, `entities_path`, `schema_path`) in its
flattened `adapter_config` is rejected with
`runtime_error:manifest_invalid`, and a `cedar` policy must declare
exactly one of `policy_set` or `policy_path` and may not carry the
`query` field as a non-object.

The prepared-invocation surface for cedar lands in the next commit
(M2.S2 D2). To keep this commit compilable and to preserve the
fail-closed contract in the interim, `prepare_policy_invocation`
returns `runtime_error:policy_invocation_failed` for any cedar binding
that reaches it. No existing rego, test, or custom path changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Mohammad Haroon Abuomar <MohammadHaroonAbuomar@users.noreply.github.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/spec): publish approval section JSON schema (D5)

Add policy-engine/spec/schema/approval.schema.json (draft 2020-12)
describing the AGT D5 top-level approval section shape: default_resolver,
timeout_seconds, on_timeout enum, fatigue_threshold,
fatigue_window_seconds, and named resolvers with a required type
discriminator plus open additional properties.

Reference the new schema from manifest.schema.json as an optional
approval property so manifest validators (current and future) load it
through the existing schema tree. The engine still treats resolver
configuration as opaque per SPECIFICATION-AGT-DELTA D5; the schema
validates shape only.

Refs M2.S4, AGT 5.0 architecture-exploration Q13.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: MohammadHaroonAbuomar <MohammadHaroonAbuomar@users.noreply.github.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/core): add CedarPolicyInvocation prepared variant

Wire the cedar branch of `prepare_policy_invocation` to its own
`PreparedPolicyInvocation::Cedar(CedarPolicyInvocation)` variant rather
than the M2.S2 D1 placeholder error. The prepared invocation carries
the resolved cedar policy source (`policy_set` xor `policy_path`), the
optional `entities_path` / `schema_path` artefacts, the optional
request-template `query` from the policy definition, the final policy
input the runtime built for this intervention point, and the canonical
JSON serialization of that input.

`engine_type()` returns the new `cedar` constant and `policy_input()`
exposes the input for the cedar arm, keeping the prepared-invocation
surface symmetrical across all four policy types.

The dispatcher trait and the CedarTestDispatcher reference
implementation land in the next commit (M2.S2 D3). The existing
OpaPolicyDispatcher continues to reject non-rego invocations with
`runtime_error:policy_invocation_failed` per SPECIFICATION.md §12.3,
so a cedar binding bound to the OPA dispatcher still fails closed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Mohammad Haroon Abuomar <MohammadHaroonAbuomar@users.noreply.github.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/policy/lib): add agt.budgets stock helpers

Reads input.snapshot.envelope.budgets per AGT-SNAPSHOT-1.0.md §1 and
emits AGT deny verdicts (SPECIFICATION-AGT-DELTA.md §D1) when any host
tracked counter has reached its configured limit. Provides individual
predicates (max_tool_calls_exceeded, max_tokens_exceeded,
timeout_exceeded, max_cost_exceeded) and a combined
deny_if_budget_exceeded helper for the M6 GovernancePolicy migration
path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/policy/lib): add agt.patterns regex helpers

PII regex constants track the canonical source list in
agent-os/src/agent_os/integrations/base.py::PII_PATTERNS (SSN, email,
phone, credit card, secret). Provides matches_any, first_match (which
returns the earliest matching span across all patterns), and a
deny_if_pattern helper that yields the AGT deny verdict shape from
SPECIFICATION-AGT-DELTA.md §D1. The earliest selector breaks ties on
pattern index so verdicts are deterministic across SDKs and OPA
versions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/policy/lib): add agt.content_hash gate

Reads input.tool.content_hash (manifest tool catalog) and
input.snapshot.tool_call.content_hash (per AGT-SNAPSHOT-1.0.md §2.5)
and denies with reason tool_content_hash_mismatch when the snapshot
hash is missing or differs from the manifest-declared hash. Returns no
verdict when the manifest did not declare a hash, so the helper is
safe to include unconditionally in the default policy.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/policy/lib): add agt.egress allowlist gate

Reads input.tool.security_labels as an allowlist of permitted egress
hosts (per SPECIFICATION §11 a tool entry MAY set security_labels) and
resolves the call destination from a small list of common snapshot
paths under input.snapshot.tool_call.args plus the
input.annotations.egress.destination override. Hosts may pass their
own destination_paths and allowlist via the rules argument.
glob.match handles wildcard entries such as *.example.com without
requiring authors to spell out every subdomain.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/policy/lib): add agt.drift warn gate

Reads input.annotations.drift_score (host-supplied annotator output,
range 0..1) and produces an AGT warn verdict with reason
drift_detected when the score reaches the configured threshold. The
helper returns nothing when the annotation is absent or non-numeric so
callers can chain it into a default policy without false positives.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/policy/lib): add agt.confidence deny gate

Reads input.annotations.confidence.score (host-supplied annotation,
range 0..1) and emits an AGT deny verdict with reason
confidence_below_threshold when the score falls below the manifest
configured minimum. Returns nothing when the annotation is absent or
non-numeric so the policy falls through to allow on missing signal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/policy/lib): add agt.redact transform helper

Combines agt.patterns.first_match with the AGT transform verdict shape
(SPECIFICATION-AGT-DELTA.md §D1.1). The returned verdict carries
transform.path equal to $policy_target and a fully replaced value, so
the dispatcher applies the substitution without host side logic. The
substitution runs in Rego via a single regex.replace over the
combined pattern alternation, which keeps redaction deterministic
across SDKs and avoids the recursive rule restriction in OPA.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/policy/lib): add agt.approval escalate helpers

Produces AGT escalate verdicts (SPECIFICATION-AGT-DELTA.md §D1.2) that
the host approval path (§17.1) resolves through the resolver declared
in the approval manifest section (§D5). escalate_if guards a verdict
on a host supplied condition; escalate_if_approver_required emits the
approval_required reason when the manifest names a non-empty approver
list; escalate_with_message carries a free form human message.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/policy/lib): add agt.ifc stock library

AGT stock IFC label-flow library. The function surface (dominates,
max_sensitivity, flow_allowed, allow, deny, verdict,
verdict_propagating, and their _with_lattice variants) mirrors the
upstream agent_control_specification.lib.ifc package so policies
written against the AGT helpers stay familiar, but the snapshot paths
are AGT-correct per AGT-SNAPSHOT-1.0.md §2.2 and §2.7. The library
exposes source_labels and result_labels convenience accessors that
read input.snapshot.input.ifc.source_labels and
input.snapshot.response.ifc.result_labels respectively, plus an
allow_if_dominates shorthand for the no write down policy. The
upstream library reads input.snapshot.ifc.* which AGT does not
populate, so AGT users MUST import data.agt.ifc instead.

The upstream policy/lib/ifc.rego and policy/lib/ifc_test.rego are kept
in place because examples/ifc_agent and the spec-18-ifc conformance
case references still depend on the upstream package name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/policy/lib): add agt.defaults implicit policy

Default verdict rule for hosts that do not author their own Rego. The
manifest binds its rego policy to data.agt.defaults.verdict and
supplies thresholds, allowlists, and pattern lists under
data.agt.defaults.config (loaded as an OPA data document). The rule
chains every AGT stock helper in severity order: ifc deny > confidence
deny > budgets deny > content_hash deny > egress deny > pattern deny
> approval escalate > redact transform > drift warn > allow. The cfg
helper avoids a self recursive rule by reading
data.agt.defaults.config from outside the package namespace. This is
the GovernancePolicy auto translation target for the M6 migration
tool.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/policy/lib): add run_tests.sh local test runner

Invokes opa test against every library file and its sibling _test.rego
in policy-engine/policy/lib. Honors OPA_BIN override, falls back to
~/.local/bin/opa when opa is not on PATH, and exits non-zero on test
failure so CI can gate on the result.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/core): add CedarPolicyDispatcher trait and CedarTestDispatcher

Land the dispatcher surface for the AGT D3 built-in cedar policy type:

- `CedarPolicyDispatcher` is the host-facing trait parallel to the rego
  dispatcher path in `opa.rs`. Implementations evaluate a
  `CedarPolicyInvocation` and return a verdict-shaped JsonValue that
  the runtime normalizes through `normalize_policy_output`.
- `build_cedar_request` implements the AGT D3.2 default mapping. The
  principal is `Agent::"<envelope.agent.id>"`, the action is
  `Action::"<intervention_point>"`, the resource is `Tool::"<name>"`
  when a tool is projected and `PolicyTarget::"<kind>"` otherwise, and
  the context keys are the snapshot keys (minus envelope) plus the
  `annotations.*` keys. The source paths follow
  `spec/agt/AGT-SNAPSHOT-1.0.md` §1.
- `CedarTestDispatcher` is the deterministic test double tests can drive
  per D3.3. It parses `policy_set` as a small JSON pseudo-cedar document
  (rules with `effect`, `principal`, `action`, `resource`, optional
  `reason`, optional `advice`), builds the cedar request, matches rules
  by entity equality (forbid wins, then first permit), and emits an
  allow, deny, or advice-translated verdict.
- `translate_advice` validates the AGT D3.3 advice shape (verdict in
  warn / escalate / transform, transform body required for transform,
  string-typed reason and message) and converts advice JSON into the
  verdict JSON the runtime expects. Path-in-$policy_target validation
  remains in `verdict::Transform::from_value` so a transform advice with
  a path outside `$policy_target` fails closed with
  `runtime_error:transform_target_forbidden` exactly like any other
  transform verdict, keeping the error contract centralized.

Both dispatchers also implement `PolicyDispatcher` so a host can swap a
cedar dispatcher in directly behind the runtime, the same way
`OpaPolicyDispatcher` does for rego.

The feature-gated `CedarBuiltinDispatcher` backed by the upstream
`cedar-policy` 4.x crate is deferred to a follow-up milestone. I could
not validate that build path in the current dev environment: the `cc`
on `PATH` is a `zig cc` wrapper that rejects the
`--target=x86_64-unknown-linux-gnu` target query that `cc-rs` passes
when compiling the `psm` transitive dependency of `cedar-policy`'s
`stacker` dep. The prompt explicitly allows this fallback. The trait
surface, the test dispatcher, and the manifest plumbing land now;
hosts that need real cedar evaluation today implement
`CedarPolicyDispatcher` themselves and link `cedar-policy` at the host
crate level. The builtin lands once the dev container ships a real
`gcc` or once we pin to a cedar-policy version whose deps avoid the
`stacker` / `psm` chain.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Mohammad Haroon Abuomar <MohammadHaroonAbuomar@users.noreply.github.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/spec): add AGT D3.3 cedar advice JSON schema

Land the normative JSON schema for the AGT D3.3 cedar advice payload at
`policy-engine/spec/schema/cedar_advice.schema.json`. The schema is
draft 2020-12, matches the artefact set under `spec/schema/wire/`, and
captures the contract `SPECIFICATION-AGT-DELTA.md` §D3.3 fixes:

- `verdict` is required and limited to `warn`, `escalate`, or
  `transform`. The `allow` and `deny` decisions never come from
  advice; cedar's own authorization result drives those.
- `reason` is optional and MUST NOT use the reserved
  `runtime_error:` prefix.
- `message` is optional and free form.
- `transform` is the AGT D1.1 single target replacement body. It is
  required when `verdict` is `transform` and forbidden otherwise. The
  conditional is expressed with an `allOf` / `if` / `then` / `else`
  block so that a malformed advice fails closed at validation time
  rather than at `Transform::from_value` time. `transform.path` MUST
  be rooted at `$policy_target`; the runtime still enforces the
  path-in-target invariant inside `normalize_policy_output` and emits
  `runtime_error:transform_target_forbidden` for a violating path.

The cedar dispatcher (see `core/src/cedar.rs::translate_advice`)
performs the same shape validation in Rust to keep the manifest-loader
and dispatcher boundaries independent of the JSON schema artefact at
runtime; the schema artefact is the canonical documentation source for
SDKs and policy authors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Mohammad Haroon Abuomar <MohammadHaroonAbuomar@users.noreply.github.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/core): cover cedar manifest validation and D3.3 dispatcher

Add the AGT M2.S2 D5 test coverage for the new cedar surface.

Manifest validation in `policy.rs::cedar_manifest_tests`:

- rego policy with `policy_set` is rejected with
  `runtime_error:manifest_invalid`
- rego policy with `policy_path` is rejected with the same reason
- cedar policy with the rego-shaped `bundle` field is rejected at
  deserialization via `deny_unknown_fields`
- cedar policy with neither `policy_set` nor `policy_path` is rejected
- cedar policy with both `policy_set` and `policy_path` is rejected
- positive coverage: cedar with only `policy_set` or only
  `policy_path` (plus optional entities and schema paths) parses cleanly
- cedar with an unknown field is rejected by `deny_unknown_fields`
- cedar with a non-object `query` is rejected

Dispatcher behaviour in `cedar.rs::tests`:

- AGT D3.2 default mapping: principal is
  `Agent::"<envelope.agent.id>"`, action is
  `Action::"<intervention_point>"`, resource is
  `Tool::"<name>"` when a tool is projected and
  `PolicyTarget::"<kind>"` otherwise, context keys exclude envelope
- a missing envelope agent id fails closed with
  `runtime_error:policy_invocation_failed`
- AGT D3.3 allow path: a permit rule with no advice produces a normalized
  `Decision::Allow` verdict
- AGT D3.3 deny path: a forbid rule wins over a permit rule and the
  rule reason flows through to the verdict reason
- no matching rule emits `deny` with `no_matching_policy`
- AGT D3.3 advice translation produces `transform`, `escalate`, and
  `warn` verdicts with reason and message preserved
- malformed advice fails closed with
  `runtime_error:policy_output_invalid` for: missing `verdict`,
  unknown `verdict`, transform without body, and warn / escalate
  carrying a transform body
- AGT D1.1 confinement: transform advice with a path outside
  `$policy_target` fails closed with
  `runtime_error:transform_target_forbidden` after
  `normalize_policy_output` re-validates the dispatcher output, proving
  the existing path-in-target check covers the cedar advice path with
  no duplicate logic
- dispatcher error paths: missing inline `policy_set`, invalid policy
  set JSON, and non-cedar invocations routed through the
  `PolicyDispatcher` trait all fail closed with
  `runtime_error:policy_invocation_failed`

All 169 `agent_control_specification_core` tests pass (29 added by this
commit, 140 pre-existing). `cargo clippy --all-targets -- -D warnings`
on the core crate is clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Mohammad Haroon Abuomar <MohammadHaroonAbuomar@users.noreply.github.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(agt-policies): add manifest_resolution layer per AGT-RESOLUTION-1.0 (M3.S1)

M3.S1 from plan v3. New top-level Python package agt-policies (5.0.0a1)
that hosts AGT 5.0 host-side primitives over the vendored ACS engine.

This commit lands the manifest resolution layer:

  agt.manifest_resolution.discover   - §2.1 governance.yaml walk with
                                       path-traversal fail-closed (not
                                       v4's empty-list-allow)
  agt.manifest_resolution.scope      - §2.3 glob-based scope filter
  agt.manifest_resolution.merge      - §2.4 rule merge preserving the
                                       deny-immutability invariant
                                       across chains; same-name-without-
                                       override drops the child
  agt.manifest_resolution.build      - §2.5 end-to-end resolve_manifest
                                       that materializes a generated Rego
                                       bundle under .agt/resolved-bundle/
                                       and emits a flat ACS manifest with
                                       extends:[] ready for the engine
  agt.manifest_resolution.errors     - D6 reserved resolution reasons
                                       wired as a ResolutionError class

29 pytest tests covering: path-traversal fail-closed; root-first
ordering; scope glob normalization; rule merge with deny-immutability
and same-name-no-override drops; top-level section merges; end-to-end
bundle materialization with sha256 sidecar; intervention point
annotations union; inherit:false truncation; reserved reason strings
matching D6 byte for byte.

29 passed in 0.12s.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(agt-policies): scenario test harness + 15 scenario tests (M5.S1)

Adds a thin OPA-subprocess harness under agt._harness/ that loads a
governance.yaml chain, runs the agt.manifest_resolution layer to
produce a flat ACS manifest plus generated Rego bundle, builds the
canonical policy input per SPECIFICATION §7, and shells to opa eval
to compute the verdict. The harness will be replaced by the Rust core
dispatcher in M3.S3; the scenario tests on top will not change.

Snapshot helpers (agt._harness.snapshot) cover all eight intervention
points per AGT-SNAPSHOT-1.0 with the envelope/budgets shape.

Scenario coverage (15 tests, all green):

  test_bank_agent_scenarios.py (6 tests)
    - wire transfer under, at-boundary, over limit
    - budget exhaustion escalates
    - higher-priority deny wins when two rules match
    - child override CANNOT defeat a parent deny across the
      AGT-side resolution chain (deny-immutability invariant)

  test_egress_content_hash_escalation_scenarios.py (6 tests)
    - egress allowlist (deny evil.com, allow api.example.com)
    - production deploy escalates per D5 with an approval block
    - matching/tampered content_hash gate (Ona/Veto defense)

  test_pii_redaction_transform_scenarios.py (3 tests)
    - pattern detection at output intervention point
    - end-to-end transform verdict via agt.redact + agt.patterns
      stock library: D1.1 path rooted at $policy_target, value is
      the [REDACTED]-substituted string, SSN and email both stripped

Implementation fix to agt.manifest_resolution.build._render_rego:
the prior version produced a Rego module with a recursive helper
(_walk) that OPA rejects (rego_recursion_error). Rewritten to inline
per-rule object.get accessors so no recursion is needed. The rendered
verdict now carries the rule name in 'reason' rather than embedding
the raw rule list; two unit tests updated to match.

Test totals after this commit: pytest 44, cargo 169, opa 98 = 311.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* fix: address M2+M3+M4 multi-model review round-1 (subset)

Both reviewers (claude-opus-4.7-1m-internal + gpt-5.5) found
overlapping concerns. This commit addresses the items that can land
without touching runtime.rs.

Blockers fixed:
  - manifest.schema.json missing cedar branch (GPT #3). Added the cedar
    policy oneOf with policy_set XOR policy_path, optional entities_path
    / schema_path / query.
  - Evidence over 4 KiB silently accepted (GPT #2 partial). Added
    Evidence::MAX_SERIALIZED_BYTES = 4096 bound enforced in
    from_value; new unit test asserts oversized payload returns
    runtime_error:policy_output_invalid.

Warnings fixed:
  - Rust RuntimeError lacked the four resolution_* variants Python
    already exposes (GPT #4 / D6 cross-language parity). Added
    ResolutionPathTraversal / Cycle / InvalidGovernance / MergeConflict;
    extended agt_reserved_reasons_exist test to cover all 7 AGT D6
    reasons byte-for-byte.
  - agt-policies build.py silently dropped rules with unsupported
    operators (Opus #6). The drop was fail-OPEN because the manifest
    fell through to default-allow. Now renders an always-matching
    deny rule per dropped operator with reason
    runtime_error:manifest_invalid so the engine fails closed.
  - Decision::applies_effects() included Escalate (Opus #7). Spec §13.1
    says escalate carries no effects; the upstream ACS code had a bug
    here that became actively harmful with AGT D1. Removed Escalate;
    explicit Transform also returns false (uses verdict.transform
    instead). Parity fixture + test updated to match.
  - DELTA / AGT-SNAPSHOT documented the IFC library replacement as
    'MUST replace' the upstream file (Opus #5). Reframed as 'AGT ships
    agt_ifc.rego alongside upstream ifc.rego'; AGT users MUST import
    data.agt.ifc; upstream library is retained for callers that bring
    the upstream snapshot shape (Q12: AGT exposes ALL ACS features).

Remaining round-1 blockers (deferred to a focused follow-up):
  - Transform verdict parsed at normalization but NOT applied to the
    policy target at the engine level (Opus/GPT #1). Adding the
    application path requires changes to runtime.rs::evaluate_intervention_point.
  - Effects[] still accepted/applied by the engine (Opus #2). D1 says
    MUST reject. Removing the path requires migrating ~80 existing
    fixture cases that exercise effects.
  - Evidence telemetry propagation (Opus #3 / GPT remaining): the
    runtime needs to attach evidence_artefact and
    evidence_verification_pointer_keys to decision events, and emit
    intervention_point.transformed instead of effect_applied.
  - Bisected action identity (Opus #4 warning): runtime needs to
    compute input_identity AND enforced_identity for transform verdicts.

These four cluster around the same Rust file (runtime.rs +
telemetry.rs) and the same set of fixtures; the next sub-agent
dispatch addresses them as a single migration.

Test totals after this commit: pytest 44, cargo 170, opa 98 = 312.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* test(agt-policies): add coding-agent and records-IFC scenario suites

Brings scenario coverage to 25 tests (10 new) across 5 archetypes:
  - bank (6 tests, already shipped)
  - egress + content_hash + escalation (6 tests, already shipped)
  - PII redaction via transform verdict (3 tests, already shipped)
  - coding agent (6 new tests): file_write to .env/secrets/ denied;
    rm -rf shell escalates; post_tool_call duration budget gate via
    post_tool_call intervention point
  - records / IFC (4 new tests): confidential-record-to-public-sink
    denied (no-write-down); TOP_SECRET refused at input intervention
    point; clean input passes

These tests exercise the manifest_resolution layer end-to-end through
the OPA dispatcher. The harness in agt._harness will swap for the
Rust core dispatcher in M3.S3 without changing any of the scenario
tests above this line.

Total tests: pytest 54, cargo 170, opa 98 = 322.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* test(agt-policies): add stock-library smoke scenarios (5 tests)

Imports each stock Rego library (agt.budgets, agt.patterns, agt.egress,
agt.drift, agt.confidence, agt.approval, agt.redact, agt.ifc) from a
host-authored Rego policy and asserts the package compiles and the
expected helper data is reachable.

Special case: test_stock_agt_ifc_library_uses_correct_paths verifies
that the AGT stock IFC library reads input.input.ifc.source_labels
(AGT-correct per AGT-SNAPSHOT-1.0 §2.2) rather than the upstream
input.snapshot.ifc.source_labels. The upstream ACS library is
preserved at policy/lib/ifc.rego (per Q12: AGT exposes ALL ACS
features); the AGT version at policy/lib/agt_ifc.rego is the one
AGT manifest authors MUST import.

Scenario coverage: pytest 30 (was 25); cargo 170; opa 98. Total 298.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/core): apply Transform verdict in runtime (AGT D1.1)

Wire the AGT D1 `transform` decision through the runtime: when
`evaluate_intervention_point_inner` sees `Decision::Transform`, resolve
`verdict.transform.path` against the current policy_target, write
`verdict.transform.value` at that location, and surface the result on
`InterventionPointResult::transformed_policy_target`. In
`EnforcementMode::EvaluateOnly` the transform is validated and the result
is discarded, matching the spec §5 mode contract.

Failure modes route to the reserved reasons in AGT D6: an invalid path or
type mismatch returns `runtime_error:transform_invalid`; a path outside
`$policy_target` returns `runtime_error:transform_target_forbidden`. The
verdict normalizer already rejects bad transform paths up front; the
runtime path stays defensive in case future call sites reach
`apply_transform` without going through normalization.

Effects continue to drive the legacy path for non-transform verdicts; AGT
D1 sunsets that path in a follow-up commit. Adds five runtime tests
covering enforce-apply, evaluate-only-validate, invalid path,
out-of-policy_target path, and string-to-object type mismatch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Copilot CLI <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/core)!: reject effects[] in dispatcher output (AGT D1)

Per `SPECIFICATION-AGT-DELTA.md` D1, the `effects` array is removed
from the verdict surface. `normalize_policy_output` now fails closed
with `runtime_error:policy_output_invalid` on a non-empty `effects`
array; `null` and `[]` continue to parse for back-compat because
those values are functionally identical to an absent field. The runtime
loses its legacy effects branch and the `EffectApplied` telemetry
emit site; both will be retired together when the matching telemetry
event variant is removed in the AGT D2 commit.

`Verdict.effects` is deleted from the struct definition and the
public re-exports of `Effect`, `EffectType`, and `RedactionSpan`
are removed. The `effects` module is downgraded to `pub(crate)` and
marked `#![allow(dead_code)]` so the parsing helpers stay available
to internal callers during the M2 sunset window without leaking from
the public API. `Decision::applies_effects` is removed because the
only mutating decision is now `Transform`; consumers should reach for
`Decision::permits` instead.

Cascade migration:

- Bank agent rego (`examples/bank_agent/policy/bank_agent_rego.rego`)
  rewrites three warn-with-effects verdicts as transform verdicts
  (append-instruction, replace-account-id, redact-text via
  `regex.replace`). The bank_agent end-to-end test follows the same
  reshape.
- Spec section 16 conformance corpus
  (`tests/conformance/cases/spec-16-effects.case-*.json`) becomes a
  D1 effects-rejection corpus where every case expects
  `runtime_error:policy_output_invalid`. `coverage.md` is updated
  to describe the new claim.
- `fail_closed_error_parity.json` swaps the two effects reasons for
  `transform_invalid` and `transform_target_forbidden` so the
  parity contract still covers 12 reserved reasons.
- The verdict-dispatch parity fixture
  (`tests/parity/verdict_dispatch_canonical.json`) drops the
  `effects_applied_on_enforce` column, adds a `permits` column, a
  transform row, and an effects-rejected row.
- Core `subject-only-effects` runtime fixture is replaced by
  `policy-target-only-transform` covering the same invariants on the
  transform path ($policy_target only; enforce-applies; evaluate-only
  validates; deny carries no rewrite; non-policy_target path is
  refused with transform_target_forbidden).
- Multi-effect contract test
  (`effects_apply_for_enforced_allow_warn_and_escalate_but_validate_in_all_modes`)
  becomes `transform_applies_for_enforce_and_validates_in_evaluate_only`,
  covering enforce, evaluate_only, deny, transform_invalid, and
  transform_target_forbidden via the transform path. Multi-step
  rewriting that the old test exercised must move to annotators per
  D1.3 (M5 follow-up).
- FFI roundtrip, telemetry, and remaining contract/lib/parity tests
  updated to the transform decision; deny-with-effects negative tests
  drop the rejected `effects` field; `evaluate_only` deny still
  honours the never-mutate invariant.
- Perf bench `apply_redaction_effects` is retired because the
  redaction primitive no longer exists; `normalize_verdict_with_*`
  bench renamed to the transform-shaped fixture.

Four new verdict.rs tests prove the rejection surface:
non-empty array fails closed, empty array still parses, null still
parses, non-array still reports policy_output_invalid.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Copilot CLI <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/core): propagate evidence and emit transformed event (AGT D2)

Per `SPECIFICATION-AGT-DELTA.md` D2 and `AGT-EVIDENCE-1.0.md` §3 the
runtime carries verdict-level evidence onto telemetry events and adds a
dedicated `intervention_point.transformed` event for transform
verdicts. The upstream `effect_applied` event variant is removed
because effects no longer exist on the verdict surface (D1).

Telemetry surface changes in `core/src/telemetry.rs`:

- `TelemetryEventType::EffectApplied` removed; wire name
  `effect_applied` no longer emitted by the core.
- `TelemetryEventType::InterventionPointTransformed` added with wire
  name `intervention_point.transformed` matching AGT-EVIDENCE-1.0 §3
  Table 2.
- `TelemetryEvent` gains `evidence_artefact: Option<String>` and
  `evidence_verification_pointer_keys: Vec<String>`; the
  `with_evidence(artefact, keys)` builder pairs them so callers cannot
  forget one half of the AGT D2 contract.
- Three unit tests in `telemetry.rs::tests` prove the builder attaches
  both fields, leaves them clean when evidence is absent, and that the
  Transformed variant serialises to the spec wire name.

Runtime integration in `core/src/runtime.rs::emit_decision_event`:

- When the verdict carries `evidence`, the base Decision event is
  decorated with the verbatim `artefact` string and the sorted
  pointer keys produced by `Evidence::pointer_keys()`. URL values
  stay out of telemetry per §3 to keep cardinality bounded.
- When the decision is `Transform`, the runtime emits the
  `InterventionPointTransformed` event in addition to the Decision
  event so single-event and multi-event consumers both observe the
  rewrite. Both events carry identical evidence metadata.
- Three integration tests in `runtime.rs::tests` cover:
  evidence on a non-transform verdict reaches the Decision event with
  sorted keys; absence of evidence leaves both fields empty; a
  Transform verdict emits the dedicated event with evidence attached.

Parity and documentation updates:

- `docs/logging-style-guide.md` and
  `tests/parity/telemetry_redaction_canonical.json` swap the
  `effect_applied` vocabulary entry for
  `intervention_point.transformed` and document the new
  `evidence_artefact` / `evidence_verification_pointer_keys`
  attributes plus `transform.value` and
  `evidence.verification_pointers.<url>` in the withheld list.
- `docs/observability.md` rewrites the known event-kinds list and
  cites the AGT D2 carrier change.
- `parity_canonical.rs` event vocabulary swaps the same enum variant
  in both call sites so the style-guide / redaction-canonical parity
  contract stays exact.
- `contract.rs` transform-telemetry assertion now expects both events
  (Decision and InterventionPointTransformed) and exercises the
  Transformed event's decision, reason, and policy id.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Copilot CLI <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/core): bisect action identity per AGT D1.4

Per `SPECIFICATION-AGT-DELTA.md` D1.4 the engine now produces two
SHA-256 identities for every successful evaluation:

- `input_identity` pins the canonical policy input that the policy
  actually saw. Equal to today's single `action_identity` for any
  non-transform verdict.
- `enforced_identity` pins the canonical policy input with
  `policy_target.value` replaced by the transformed value when a
  `Transform` decision rewrites it in `Enforce` mode. Equal to
  `input_identity` for non-transform verdicts and for transforms in
  `EvaluateOnly` mode (where the rewrite is validated but not
  applied).

`InterventionPointResult` grows two fields, `input_identity` and
`enforced_identity`; the existing `action_identity` field becomes
the backwards-compatible alias for `enforced_identity` per
AGT-EVIDENCE-1.0 §3 ("single-identity telemetry consumers MAY default
to enforced_identity"). Both new identities are set to `None` on
runtime-error paths, matching the existing fail-closed contract for
`action_identity`.

Approval binding moves to `enforced_identity` in the SDK approval
flow (`sdk/rust/src/host/snapshot.rs`). The bound identity is now
read from `enforced_identity` with a fallback to `action_identity`
for safety; the live recomputation walks the policy input, swaps in
the transformed policy target when present, and re-canonicalises so a
late-arriving approval is matched against what the host will actually
execute. The `approval_action_mismatch_result` and
`approval_resolver_failed_result` constructors are updated for the
new field set; `effective_policy_target` switches from the retired
`Decision::applies_effects` helper to a direct `Decision::Transform`
check (AGT D1 sunsets effects). Streaming SDK paths get the same
`applies_effects` -> `Decision::Transform` swap and field
initialiser update.

Four new runtime tests cover D1.4:

- non-transform verdict yields equal `input_identity` and
  `enforced_identity` (and `action_identity` matches);
- transform that mutates the policy target shifts
  `enforced_identity` away from `input_identity`;
- evaluate-only transform keeps `enforced_identity ==
  input_identity` because the rewrite is validated but not applied;
- runtime errors clear all three identity fields.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Copilot CLI <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* fix(policy-engine): strict effects rejection + parity fixture for new D6 reasons

Round-2 multi-model review consensus blockers (small subset):

  - Strict AGT D1: reject any presence of the effects key on a
    verdict, including empty arrays and explicit nulls. The previous
    back-compat carve-out was loose; dispatchers MUST now drop the
    effects key entirely.
    Tests updated to assert empty[] and null both fail closed.

  - tests/parity/error_mapping_canonical.json was the only fixture
    not migrated by commit 335f7c7b. Added the 7 new D6 reasons
    (resolution_path_traversal/cycle/invalid_governance/merge_conflict,
    transform_target_forbidden, transform_invalid,
    approval_resolver_missing) and removed the deleted
    effect_invalid / effect_target_forbidden entries.

Larger SDK propagation blockers (Python + Node SDKs missing Transform
decision; FFI surfacing only action_identity not the new bisected
input_identity/enforced_identity) are dispatched separately to a
focused sub-agent.

Test suite: cargo 189 still passing 0 failing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* test(policy-engine/core): align parity_canonical with strict effects fixture

The a9a9ddc8 strict effects rejection commit refreshed
tests/parity/error_mapping_canonical.json to enumerate the 18 reserved
reasons the AGT surface currently emits (it dropped the legacy
effect_invalid and effect_target_forbidden pair and added the seven
AGT D5/D6 reasons). The accompanying parity_canonical test still
asserted a 13-row fixture and only populated the runtime_errors map
with the pre-AGT 13 variants, so cargo test now fails closed at
canonical_error_mapping_matches_core_and_spec.

Update runtime_errors to map all 18 AGT-era variants byte for byte
with the fixture and accept their reason strings from either
SPECIFICATION.md or SPECIFICATION-AGT-DELTA.md (the AGT D5 and D6
reasons live in the delta document). The legacy EffectInvalid and
EffectTargetForbidden variants stay on RuntimeError for back-compat
but no longer appear in the parity fixture per AGT D1.

cargo test -p agent_control_specification_core now reports 189/189
passing, matching the M2 review baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/core/ffi): expose bisected identity and evidence on evaluate response

AGT D1.4 split the engine action identity into input_identity and
enforced_identity, and AGT D2 added optional verdict.evidence plus the
existing verdict.transform payload. core/src/runtime.rs already
populates all four fields on InterventionPointResult and Verdict, but
acs_runtime_evaluate only serialized action_identity, transformed
policy target, policy input, and the verdict's serde shape.

Extend the JSON response shape with input_identity and
enforced_identity. Keep action_identity as a backwards-compatible alias
for enforced_identity so older SDK bindings continue to deserialize a
well-formed result without a breaking change. verdict.transform and
verdict.evidence already ride through this response verbatim through
serde on Verdict; the policy callback in the roundtrip test now
exercises both fields.

ffi_roundtrip_transforms_policy_target now asserts:

- verdict.transform.path and verdict.transform.value are propagated
- verdict.evidence.artefact and verdict.evidence.verification_pointers
  are propagated verbatim
- input_identity and enforced_identity are both present and differ
  because the transform mutated the policy target
- action_identity equals enforced_identity for back-compat

ffi_roundtrip_allow_carries_evidence_and_matched_identities is added
to cover the non-transform path: an allow verdict carries the same
identity in both slots and still ships any evidence the dispatcher
attached.

cargo test -p agent_control_specification_core: 190 passing (189
baseline + 1 new FFI test). No tests regress.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/sdk/python): support Transform decision + bisected identity + evidence

AGT D1 added the Transform decision as the sole mutating verdict, AGT
D1.4 split action identity into input_identity and enforced_identity,
and AGT D2 added an optional Evidence payload that high-assurance
dispatchers attach to a verdict. The Python SDK was still on the
pre-AGT shape and gated effects on applies_effects across allow, warn,
and escalate. Bring the SDK into line with the Rust core.

Types (_types.py)

- Decision.TRANSFORM is added; Decision.permits matches the Rust core
  (allow|warn|transform). Decision.applies_transform is the canonical
  mutating predicate (only TRANSFORM). Decision.applies_effects stays
  as a deprecated alias that returns applies_transform and emits a
  DeprecationWarning so out-of-tree callers see a clear migration
  signal.
- Transform dataclass mirrors core/src/verdict.rs::Transform with a
  from_mapping constructor.
- Evidence dataclass mirrors core/src/verdict.rs::Evidence with a
  from_mapping constructor that validates artefact type and pointer
  value types.
- Verdict now carries optional transform and evidence fields; the
  legacy effects sequence is removed because AGT D1 rejected it.
  Verdict.from_mapping parses both shapes and surfaces typed objects.
- InterventionPointResult now carries input_identity and
  enforced_identity per AGT D1.4. action_identity becomes a property
  alias that returns enforced_identity (the action that actually ran),
  satisfying AGT-EVIDENCE-1.0 §4's single-identity fallback.

Client (_client.py)

- NativeRuntimeClient.evaluate_intervention_point now reads
  input_identity and enforced_identity from the FFI response and
  falls back to action_identity when an older native binary only
  exposed the single field, so rollouts stay tolerant of stale
  bindings.

Orchestration (_orchestration.py, _adapters/_shared.py)

- _transformed_or now gates on applies_transform per AGT D1; ESCALATE
  no longer routes through a fallback that depended on the old
  applies_effects semantics. The litellm and openai adapter sites
  follow the same migration so streaming transforms only fire on a
  TRANSFORM verdict.

Tests (sdk/python/tests/)

- test_parity_canonical now reads the new permits column from
  verdict_dispatch_canonical.json and verifies the SDK surface for the
  new TRANSFORM row. The error_mapping_canonical assertion enumerates
  the seven AGT D5/D6 reserved reasons added by a9a9ddc8 and round-
  trips each through Verdict so the SDK does not choke on unknown
  reasons.
- test_escalate_allow_applies_effects_after_approval is replaced by
  test_transform_verdict_routes_through_transformed_policy_target,
  which proves the SDK uses the engine transform value without
  consulting an approval resolver.
- test_transform_evidence_identity.py is added: 14 tests covering
  Decision.TRANSFORM enum surface, Verdict.from_mapping parsing for
  transform and evidence, the deprecation warning on applies_effects,
  bisected identity persistence, action_identity property aliasing,
  end-to-end transform routing in enforce mode, evaluate_only
  bypassing transforms, warn-with-stale-target defence in depth, and
  evidence round trip from a native-shape response.
- Adapter test helpers default to Decision.TRANSFORM when a fixture
  supplies a transformed_policy_target so adapter call sites stay
  green under the tighter gate. QueueRuntime test fixtures move
  off the legacy single-identity replace pattern onto the bisected
  fields.

PYTHONPATH=. python -m pytest sdk/python/tests -q: 102 passed, 39
skipped (native bindings unavailable in this env). Baseline was 86
passed + 2 failed + 39 skipped, so this run nets +16 new tests with
the two prior parity failures cleared.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/sdk/node): support Transform decision + bisected identity + evidence

Mirror the Python SDK migration (commit c6ce5793) on the Node side so
the TypeScript surface and the napi binding agree with the Rust core
shape produced by AGT D1, D1.4, and D2.

Wire surface (src/index.ts)

- Decision union gains 'transform' per AGT D1.
- Transform and Evidence types are added; Transform mirrors
  core/src/verdict.rs::Transform (path + value) and Evidence mirrors
  core/src/verdict.rs::Evidence (artefact + verificationPointers).
- Verdict now carries optional transform and evidence fields; the
  legacy effects[] array is removed because AGT D1 rejected it.
- InterventionPointResult carries inputIdentity and enforcedIdentity
  per AGT D1.4; actionIdentity remains a back-compat alias for
  enforcedIdentity (the action that actually executed).
- mapResult prefers the new bisected fields when the napi binding
  exposes them and falls back to action_identity for older binaries
  so rollouts stay tolerant of stale bindings.

Mutation gate (src/adapter-helpers.ts, src/streaming.ts)

- EFFECT_APPLYING_DECISIONS = [allow, warn, escalate] is replaced with
  TRANSFORM_DECISIONS = [transform]. Only TRANSFORM is allowed to
  mutate per AGT D1.
- appliesTransform is the canonical predicate; appliesEffects is a
  deprecated alias that delegates to it with a JSDoc deprecation note.
- transformedOr now gates strictly on appliesTransform. The streaming
  pipeline uses appliesTransform too so a post_model_call WARN with a
  stale transformedPolicyTarget cannot leak through.

napi binding (native/lib.rs)

- result_to_value adds input_identity and enforced_identity alongside
  the back-compat action_identity slot so the Node SDK consumes the
  same wire shape the Python SDK and FFI now produce.

Tests (sdk/node/test/)

- transform-evidence-identity.test.mjs is added: 9 tests covering the
  new Decision.Transform value, appliesTransform vs the deprecated
  appliesEffects alias, transformedOr gating allow|warn|deny|escalate
  out, evaluate_only bypass, bisected identity surface, Evidence round
  trip, end-to-end TRANSFORM routing without an approval resolver, and
  defence-in-depth dropping of stale transformedPolicyTarget on non-
  TRANSFORM verdicts.
- Existing adapter test stubs (adapters, adapter-mediation, ghcp,
  streaming-conformance) auto-pick Decision.Transform when a handler
  supplies a transformedPolicyTarget so call sites that exercised the
  old applies_effects gate stay green under the tighter mutation
  rule.
- escalation.test.mjs replaces the pre-AGT
  'escalate resolved to allow applies escalate effects after
  approval' case with a transform-routes-without-resolver test, since
  ESCALATE + transformedPolicyTarget is no longer producible under
  AGT D1.
- native-runtime.test.mjs and coding_assistant_use_case.test.mjs
  migrate every warn+effects[] fixture to transform+transform per AGT
  D1, including the dedicated 'transformedPolicyTarget +
  bisected identity' assertion for the native happy path.

Validation

- tsc -p tsconfig.json --noEmit: clean.
- node --test on all non-native test files: 49 passing
  (40 baseline + 9 new transform tests). The coding_assistant and
  native-runtime suites need the napi binary to load and are
  expected to remain skipped in environments without a built native.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* feat(policy-engine/integrations/otel): forward evidence and transformed counter per AGT D2

AGT D1 added the transform decision and AGT D2 added evidence
propagation to telemetry events per AGT-EVIDENCE-1.0 §3, including the
new intervention_point.transformed event. The OTel sink only knew
about the pre-AGT four-decision surface and dropped evidence on the
floor.

Bring the OTel integration into line with the core surface.

DECISION_WIRE_STRINGS

- Add 'transform' so the per-decision counter map ranges over all five
  AGT wire decisions (allow, deny, warn, escalate, transform). Without
  this entry the runtime's intervention_point.transformed event would
  have no counter to increment.

metric_attributes

- Add evidence_artefact: the verbatim artefact string from the
  originating verdict. Mirrors TelemetryEvent::evidence_artefact and
  AGT-EVIDENCE-1.0 §3.
- Add evidence_verification_pointer_keys: the sorted comma-joined list
  of verification pointer key names. The URL values themselves MUST
  NOT appear in telemetry per AGT-EVIDENCE-1.0 §3; auditors recover
  them from the audit record per §4.
- Both attributes are omitted when the verdict carries no evidence so
  the common no-evidence path stays clean.

Tests (in-crate)

- default_uses_canonical_meter_name asserts the per-decision counter
  count is 5 to lock in the new transform counter.
- mapping_omits_evidence_attributes_when_verdict_has_none locks the
  no-evidence path.
- mapping_includes_evidence_attributes_when_verdict_has_them verifies
  the verbatim artefact and sorted pointer keys land on the attribute
  list, and additionally asserts no attribute value contains an
  https:// URL to enforce the AGT-EVIDENCE-1.0 §3 cardinality rule.
- intervention_point_transformed_event_increments_transform_counter
  verifies the new event type emits an event_type of
  intervention_point.transformed, that the decision attribute is
  transform, that evidence rides through, and that the per-decision
  counter map contains a 'transform' entry so emit() finds a counter.

cargo test -p agent_control_specification_otel: 6 passing (was 3,
+3 new). cargo test -p agent_control_specification_core stays at
190 passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: AGT 5.0 ACS merge <agentgovtoolkit@microsoft.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>

* fix(policy-engine/examples/bank_agent): migrate demo to transform verdicts

The rego template under policy/bank_agent_rego.rego was migrated to
return transform decisions for pre_model_call, post_tool_call, and
output in 335f7c7b, and the strict runtime in a9a9ddc8 now rejects
the legacy effects[] surface as runtime_error:policy_output_invalid.
The stdlib-only mock host under demo/run_demo.py still mirrored the
pre-AGT shape, so it failed to demonstrate the actual policy and
would fail closed in any setup that ran the rego policy through the
real runtime.

STAGE_FIXTURES

- pre_model_call now asserts decision == 'transform' (was 'warn').
- post_tool_call now asserts decision == 'transform' (was 'warn').
- output now asserts decision == 'transform' (was 'warn').
- agent_shutdown stays at 'warn' (the rego policy still warns there).

evaluate_policy

- pre_model_call returns transform with a single-target replace at
  .messages, mirroring the rego array.concat semantic.
- post_tool_call returns transform with a single-target replace at
  .account_id, mirroring the rego template.
- output returns transform with a single-target replace at
  .text computed via re.sub(CHK-[0-9]+,
  ACCOUNT-REDACTED, text), mirroring the rego regex.replace.
- Every non-transform decision now MUST NOT carry a transform; this
  invariant is asserted in enforce().

enforce

- Drops effects[]; the helper now consumes the transform key on a
  transform verdict and applies it via the new apply_transform
  helper. Fails closed with AssertionError on any of:
  - presence of effects[] (AGT D1 rejected the shape).
  - transform present on a deny / escalate verdict (AGT D1.1 ban).
  - transform present on a non-transform permitting verdict (allow
    or warn carrying a stale transform is a host-side bug).
  - transform missing on a transform verdict.

apply_transform

- New helper that walks .* via the existing path_tokens
  utility and replaces the value at the resolved path. The legacy
  apply_effect / apply_effects / account_redaction_effect helpers are
  retained as fail-closed stubs that raise AssertionError so any out-
  of-tree caller of the demo's legacy API surfaces a clear migration
  hint.

Manual run

- python examples/bank_agent/demo/run_demo.py prints
  'demo verification: PASS' and the user-visible output reads
  'I canno…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant